This page is rendered from the real demo file shown below.
<script lang="ts">
import {
Avatar,
Badge,
Button,
Card,
Flex,
Grid,
Stack,
Tabs,
} from '@dryui/ui';
import { AvatarGroup } from '@dryui/ui/avatar-group';
import { ChipGroup } from '@dryui/ui/chip-group';
import { SegmentedControl } from '@dryui/ui/segmented-control';
import { StatCard } from '@dryui/ui/stat-card';
let tripType = $state('round-trip');
let activeTab = $state('results');
let filters = $state(['in-policy', 'direct']);
const flights = [
{
route: 'SFO → JFK',
airline: 'United 1942',
departure: '08:10',
arrival: '16:42',
duration: '5h 32m',
stops: 'Direct',
price: '$612',
policy: { label: 'In policy', color: 'green' as const },
travelers: ['ER', 'MC', 'JL', 'PP', 'SR'],
},
{
route: 'LAX → BOS',
airline: 'Delta 827',
departure: '09:45',
arrival: '18:25',
duration: '5h 40m',
stops: '1 stop',
price: '$548',
policy: { label: 'Needs justification', color: 'yellow' as const },
travelers: ['NB', 'CD', 'AV'],
},
];
const approvals = [
{ traveler: 'Elena Rossi', trip: 'San Francisco to New York', reason: 'Customer summit', eta: '42m' },
{ traveler: 'Maya Chen', trip: 'Los Angeles to Boston', reason: 'Board workshop', eta: '1h 18m' },
{ traveler: 'Jordan Lee', trip: 'Austin to Seattle', reason: 'Q2 kickoff', eta: '3h 05m' },
];
</script>
<Stack gap="lg">
<Flex justify="between" align="center">
<Stack gap="sm">
<h2 class="title">Corporate Travel Hub</h2>
<p class="subtitle">Travel booking, approvals, and policy tracking in one DryUI surface.</p>
</Stack>
<Flex gap="sm" align="center">
<Button variant="outline" size="sm">Export report</Button>
<Button size="sm">Create trip</Button>
</Flex>
</Flex>
<Grid columns={4} gap="md">
<StatCard.Root tone="info">
<StatCard.Label>Total travel spend</StatCard.Label>
<StatCard.Value>$613M</StatCard.Value>
<StatCard.Trend direction="up">12.4% vs last quarter</StatCard.Trend>
</StatCard.Root>
<StatCard.Root tone="success">
<StatCard.Label>Policy compliance</StatCard.Label>
<StatCard.Value>98.2%</StatCard.Value>
<StatCard.Trend direction="up">+2.1 points this month</StatCard.Trend>
</StatCard.Root>
<StatCard.Root tone="warning">
<StatCard.Label>Open approvals</StatCard.Label>
<StatCard.Value>14</StatCard.Value>
<StatCard.Trend direction="flat">3 escalations pending</StatCard.Trend>
</StatCard.Root>
<StatCard.Root tone="danger">
<StatCard.Label>Blocked bookings</StatCard.Label>
<StatCard.Value>4</StatCard.Value>
<StatCard.Trend direction="down">2 above weekly target</StatCard.Trend>
</StatCard.Root>
</Grid>
<Grid columns={3} gap="lg">
<div class="main-column">
<Tabs.Root bind:value={activeTab}>
<Tabs.List>
<Tabs.Trigger value="results">Travel Results</Tabs.Trigger>
<Tabs.Trigger value="approvals">Approval Flow</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="results">
<Card.Root>
<Card.Header>
<Stack gap="md">
<Flex justify="between" align="center" wrap="wrap">
<Stack gap="sm">
<h3 class="card-title">Search results</h3>
<p class="section-copy">Use SegmentedControl, ChipGroup, AvatarGroup, and StatCard together for travel-specific booking flows.</p>
</Stack>
<SegmentedControl.Root bind:value={tripType}>
<SegmentedControl.Item value="one-way">One way</SegmentedControl.Item>
<SegmentedControl.Item value="round-trip">Round trip</SegmentedControl.Item>
<SegmentedControl.Item value="multi-city">Multi-city</SegmentedControl.Item>
</SegmentedControl.Root>
</Flex>
<ChipGroup.Root type="multiple" bind:value={filters}>
<ChipGroup.Item value="in-policy">In policy</ChipGroup.Item>
<ChipGroup.Item value="direct">Direct</ChipGroup.Item>
<ChipGroup.Item value="wifi">Wi-fi</ChipGroup.Item>
<ChipGroup.Item value="lounge">Lounge access</ChipGroup.Item>
</ChipGroup.Root>
</Stack>
</Card.Header>
<Card.Content>
<Stack gap="md">
{#each flights as flight (flight.route)}
<Card.Root>
<Card.Content>
<Stack gap="md">
<Flex justify="between" align="start" wrap="wrap">
<Stack gap="sm">
<Flex gap="sm" align="center" wrap="wrap">
<h4 class="flight-route">{flight.route}</h4>
<Badge variant="soft" color={flight.policy.color} size="sm">{flight.policy.label}</Badge>
<Badge variant="soft" color="blue" size="sm">{flight.stops}</Badge>
</Flex>
<p class="section-copy">{flight.airline}</p>
</Stack>
<div class="price-block">
<Stack gap="sm">
<span class="price">{flight.price}</span>
<span class="section-copy">per traveler</span>
</Stack>
</div>
</Flex>
<Grid columns={3} gap="md">
<StatCard.Root density="compact">
<StatCard.Label>Departure</StatCard.Label>
<StatCard.Value>{flight.departure}</StatCard.Value>
<StatCard.Trend direction="flat">Terminal 2</StatCard.Trend>
</StatCard.Root>
<StatCard.Root density="compact">
<StatCard.Label>Arrival</StatCard.Label>
<StatCard.Value>{flight.arrival}</StatCard.Value>
<StatCard.Trend direction="flat">Local time</StatCard.Trend>
</StatCard.Root>
<StatCard.Root density="compact">
<StatCard.Label>Duration</StatCard.Label>
<StatCard.Value>{flight.duration}</StatCard.Value>
<StatCard.Trend direction="up">12 kg CO2 saved</StatCard.Trend>
</StatCard.Root>
</Grid>
<Flex justify="between" align="center" wrap="wrap">
<AvatarGroup count={flight.travelers.length} maxVisible={4} label="Travelers">
{#each flight.travelers.slice(0, 4) as traveler (traveler)}
<Avatar fallback={traveler} size="sm" />
{/each}
</AvatarGroup>
<Flex gap="sm" align="center">
<Button variant="outline" size="sm">Compare fares</Button>
<Button size="sm">Select flight</Button>
</Flex>
</Flex>
</Stack>
</Card.Content>
</Card.Root>
{/each}
</Stack>
</Card.Content>
</Card.Root>
</Tabs.Content>
<Tabs.Content value="approvals">
<Card.Root>
<Card.Header>
<h3 class="card-title">Approval chain</h3>
</Card.Header>
<Card.Content>
<Stack gap="md">
{#each approvals as approval (approval.traveler)}
<Card.Root>
<Card.Content>
<Flex justify="between" align="start" wrap="wrap">
<Stack gap="sm">
<div class="approval-name">{approval.traveler}</div>
<div class="section-copy">{approval.trip}</div>
<Badge variant="soft" color="yellow" size="sm">{approval.reason}</Badge>
</Stack>
<div class="eta-block">
<Stack gap="sm">
<Badge variant="outline" color="gray" size="sm">Escalates in {approval.eta}</Badge>
<Flex gap="sm">
<Button variant="outline" size="sm">Request info</Button>
<Button size="sm">Approve</Button>
</Flex>
</Stack>
</div>
</Flex>
</Card.Content>
</Card.Root>
{/each}
</Stack>
</Card.Content>
</Card.Root>
</Tabs.Content>
</Tabs.Root>
</div>
<div class="side-column">
<Card.Root>
<Card.Header>
<h3 class="card-title">Duty of care</h3>
</Card.Header>
<Card.Content>
<Stack gap="md">
<StatCard.Root tone="warning" density="compact">
<StatCard.Label>Travelers in transit</StatCard.Label>
<StatCard.Value>28</StatCard.Value>
<StatCard.Trend direction="flat">6 red-eye arrivals tonight</StatCard.Trend>
</StatCard.Root>
<StatCard.Root tone="danger" density="compact">
<StatCard.Label>Risk zone alerts</StatCard.Label>
<StatCard.Value>3</StatCard.Value>
<StatCard.Trend direction="down">Weather disruption in the northeast</StatCard.Trend>
</StatCard.Root>
<Card.Root>
<Card.Content>
<Stack gap="sm">
<h4 class="flight-route">Traveler cluster</h4>
<AvatarGroup count={6} maxVisible={5} label="Travelers in New York">
<Avatar fallback="ER" size="sm" />
<Avatar fallback="MC" size="sm" />
<Avatar fallback="JL" size="sm" />
<Avatar fallback="PP" size="sm" />
<Avatar fallback="SR" size="sm" />
</AvatarGroup>
<p class="section-copy">Five travelers arriving in New York before 7pm local time.</p>
</Stack>
</Card.Content>
</Card.Root>
</Stack>
</Card.Content>
</Card.Root>
</div>
</Grid>
</Stack>
<style>
.title {
margin: 0;
font-size: var(--dry-text-2xl-size);
font-weight: 700;
}
.subtitle,
.section-copy {
margin: 0;
color: var(--dry-color-text-secondary);
line-height: 1.5;
}
.card-title {
margin: 0;
font-size: var(--dry-text-lg-size);
font-weight: 600;
}
.flight-route,
.approval-name {
margin: 0;
font-size: var(--dry-text-base-size);
font-weight: 600;
}
.price {
font-size: var(--dry-text-xl-size);
font-weight: 700;
color: var(--dry-color-text);
}
.price-block,
.eta-block {
text-align: right;
}
.main-column {
grid-column: span 2;
}
.side-column {
grid-column: span 1;
}
@media (max-width: 900px) {
.main-column,
.side-column {
grid-column: span 3;
}
.price-block,
.eta-block {
text-align: left;
}
}
</style>