445 lines
12 KiB
TypeScript
445 lines
12 KiB
TypeScript
import { useState } from 'react';
|
|
import {
|
|
Button,
|
|
IconButton,
|
|
TextField,
|
|
TextArea,
|
|
SearchField,
|
|
Select,
|
|
Switch,
|
|
Checkbox,
|
|
RadioGroup,
|
|
RadioItem,
|
|
Control,
|
|
SegmentedControl,
|
|
Slider,
|
|
Stepper,
|
|
Tabs,
|
|
TabsList,
|
|
TabsContent,
|
|
Progress,
|
|
Badge,
|
|
Chip,
|
|
Card,
|
|
List,
|
|
Row,
|
|
Menu,
|
|
MenuTrigger,
|
|
MenuContent,
|
|
MenuItem,
|
|
MenuSeparator,
|
|
Tooltip,
|
|
Spinner,
|
|
Callout,
|
|
Table,
|
|
THead,
|
|
TBody,
|
|
Tr,
|
|
Th,
|
|
Td,
|
|
Dialog,
|
|
DialogClose,
|
|
AlertDialog,
|
|
Window,
|
|
useTheme,
|
|
} from '@olly/modern-sk';
|
|
|
|
const sectionStyle: React.CSSProperties = {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: '0.75rem',
|
|
};
|
|
|
|
const rowWrap: React.CSSProperties = {
|
|
display: 'flex',
|
|
flexWrap: 'wrap',
|
|
gap: '0.75rem',
|
|
alignItems: 'center',
|
|
};
|
|
|
|
const labelStyle: React.CSSProperties = {
|
|
fontSize: '0.75rem',
|
|
fontWeight: 600,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.05em',
|
|
color: 'var(--color-text-3)',
|
|
};
|
|
|
|
function Section({
|
|
title,
|
|
children,
|
|
}: {
|
|
title: string;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<Card style={{ padding: '1.25rem', ...sectionStyle }}>
|
|
<span style={labelStyle}>{title}</span>
|
|
{children}
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export function HomePage() {
|
|
const { theme, setTheme } = useTheme();
|
|
const [search, setSearch] = useState('');
|
|
const [select, setSelect] = useState<string | undefined>();
|
|
const [seg, setSeg] = useState('list');
|
|
const [tab, setTab] = useState('one');
|
|
const [vol, setVol] = useState([60]);
|
|
const [count, setCount] = useState(3);
|
|
const [chips, setChips] = useState(['rock', 'jazz', 'ambient']);
|
|
const [switchOn, setSwitchOn] = useState(true);
|
|
const [radio, setRadio] = useState('a');
|
|
|
|
return (
|
|
<div style={{ overflow: 'auto', height: '100%' }}>
|
|
<div
|
|
style={{
|
|
padding: '1.5rem',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: '1.25rem',
|
|
maxWidth: '64rem',
|
|
margin: '0 auto',
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
}}
|
|
>
|
|
<div>
|
|
<h1 style={{ margin: 0, fontSize: '1.5rem', fontWeight: 700 }}>
|
|
♫ MCMA — Component Kitchen Sink
|
|
</h1>
|
|
<p
|
|
style={{
|
|
margin: '0.25rem 0 0',
|
|
color: 'var(--color-text-2)',
|
|
fontSize: '0.875rem',
|
|
}}
|
|
>
|
|
modern-sk reference. Project base ready for development.
|
|
</p>
|
|
</div>
|
|
<Tooltip content={`Switch to ${theme === 'dark' ? 'light' : 'dark'}`}>
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
|
>
|
|
{theme === 'dark' ? '☾ Dark' : '☀ Light'}
|
|
</Button>
|
|
</Tooltip>
|
|
</div>
|
|
|
|
<Section title="Buttons">
|
|
<div style={rowWrap}>
|
|
<Button variant="key">Key</Button>
|
|
<Button variant="primary">Primary</Button>
|
|
<Button variant="ember">Ember</Button>
|
|
<Button variant="ghost">Ghost</Button>
|
|
<Button variant="primary" size="sm">
|
|
Small
|
|
</Button>
|
|
<Button variant="primary" disabled>
|
|
Disabled
|
|
</Button>
|
|
</div>
|
|
<div style={rowWrap}>
|
|
<IconButton variant="primary" aria-label="Play">
|
|
▶
|
|
</IconButton>
|
|
<IconButton variant="ghost" aria-label="Next">
|
|
⏭
|
|
</IconButton>
|
|
<IconButton variant="ember" size="lg" aria-label="Stop">
|
|
⏹
|
|
</IconButton>
|
|
<Stepper
|
|
onDecrement={() => setCount((c) => c - 1)}
|
|
onIncrement={() => setCount((c) => c + 1)}
|
|
/>
|
|
<span
|
|
style={{ fontSize: '0.875rem', color: 'var(--color-text-2)' }}
|
|
>
|
|
count: {count}
|
|
</span>
|
|
</div>
|
|
</Section>
|
|
|
|
<Section title="Inputs">
|
|
<div style={rowWrap}>
|
|
<TextField placeholder="Text field" style={{ width: '14rem' }} />
|
|
<SearchField
|
|
icon="⌕"
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
placeholder="Search…"
|
|
style={{ width: '14rem' }}
|
|
/>
|
|
<Select
|
|
placeholder="Pick genre"
|
|
aria-label="Genre"
|
|
value={select}
|
|
onValueChange={setSelect}
|
|
items={[
|
|
{ value: 'rock', label: 'Rock' },
|
|
{ value: 'jazz', label: 'Jazz' },
|
|
{ value: 'ambient', label: 'Ambient' },
|
|
]}
|
|
/>
|
|
</div>
|
|
<TextArea placeholder="Text area / description…" rows={3} />
|
|
</Section>
|
|
|
|
<Section title="Toggles & selection">
|
|
<div style={rowWrap}>
|
|
<Control
|
|
control={
|
|
<Switch checked={switchOn} onCheckedChange={setSwitchOn} />
|
|
}
|
|
>
|
|
Switch
|
|
</Control>
|
|
<Control control={<Checkbox defaultChecked />}>Checkbox</Control>
|
|
</div>
|
|
<RadioGroup
|
|
value={radio}
|
|
onValueChange={setRadio}
|
|
style={{ display: 'flex', gap: '1rem' }}
|
|
>
|
|
<Control control={<RadioItem value="a" />}>Option A</Control>
|
|
<Control control={<RadioItem value="b" />}>Option B</Control>
|
|
<Control control={<RadioItem value="c" />}>Option C</Control>
|
|
</RadioGroup>
|
|
<SegmentedControl
|
|
value={seg}
|
|
onValueChange={setSeg}
|
|
items={[
|
|
{ value: 'list', label: 'List' },
|
|
{ value: 'grid', label: 'Grid' },
|
|
{ value: 'compact', label: 'Compact' },
|
|
]}
|
|
/>
|
|
</Section>
|
|
|
|
<Section title="Sliders & progress">
|
|
<Slider
|
|
min={0}
|
|
max={100}
|
|
step={1}
|
|
value={vol}
|
|
onValueChange={setVol}
|
|
notches="bottom"
|
|
/>
|
|
<span style={{ fontSize: '0.875rem', color: 'var(--color-text-2)' }}>
|
|
value: {vol[0]}
|
|
</span>
|
|
<Progress value={vol[0]} />
|
|
</Section>
|
|
|
|
<Section title="Badges, chips, spinner">
|
|
<div style={rowWrap}>
|
|
<Badge variant="lime" dot>
|
|
On server
|
|
</Badge>
|
|
<Badge variant="ember" dot>
|
|
Error
|
|
</Badge>
|
|
<Badge variant="neutral">Neutral</Badge>
|
|
<Badge variant="outline">Outline</Badge>
|
|
<Spinner size="sm" />
|
|
<Spinner size="lg" />
|
|
</div>
|
|
<div style={rowWrap}>
|
|
{chips.map((c) => (
|
|
<Chip
|
|
key={c}
|
|
onRemove={() => setChips((prev) => prev.filter((x) => x !== c))}
|
|
>
|
|
{c}
|
|
</Chip>
|
|
))}
|
|
{chips.length === 0 && (
|
|
<span
|
|
style={{ fontSize: '0.875rem', color: 'var(--color-text-3)' }}
|
|
>
|
|
all removed
|
|
</span>
|
|
)}
|
|
</div>
|
|
</Section>
|
|
|
|
<Section title="Callouts">
|
|
<Callout variant="info">
|
|
Info — backend address resolves from runtime → env → relative
|
|
/api/v1.
|
|
</Callout>
|
|
<Callout variant="success">
|
|
Success — typecheck and lint pass clean.
|
|
</Callout>
|
|
<Callout variant="warning">
|
|
Warning — most feature screens are still stubs.
|
|
</Callout>
|
|
<Callout variant="danger">
|
|
Danger — destructive actions use AlertDialog.
|
|
</Callout>
|
|
</Section>
|
|
|
|
<Section title="Tabs">
|
|
<Tabs value={tab} onValueChange={setTab}>
|
|
<TabsList
|
|
items={[
|
|
{ value: 'one', label: 'First' },
|
|
{ value: 'two', label: 'Second' },
|
|
{ value: 'three', label: 'Third' },
|
|
]}
|
|
/>
|
|
<TabsContent
|
|
value="one"
|
|
style={{
|
|
padding: '0.75rem 0',
|
|
color: 'var(--color-text-2)',
|
|
fontSize: '0.875rem',
|
|
}}
|
|
>
|
|
First panel
|
|
</TabsContent>
|
|
<TabsContent
|
|
value="two"
|
|
style={{
|
|
padding: '0.75rem 0',
|
|
color: 'var(--color-text-2)',
|
|
fontSize: '0.875rem',
|
|
}}
|
|
>
|
|
Second panel
|
|
</TabsContent>
|
|
<TabsContent
|
|
value="three"
|
|
style={{
|
|
padding: '0.75rem 0',
|
|
color: 'var(--color-text-2)',
|
|
fontSize: '0.875rem',
|
|
}}
|
|
>
|
|
Third panel
|
|
</TabsContent>
|
|
</Tabs>
|
|
</Section>
|
|
|
|
<Section title="List & rows">
|
|
<List>
|
|
<Row style={{ padding: '0.5rem 0.75rem' }}>Track one — Artist</Row>
|
|
<Row selected style={{ padding: '0.5rem 0.75rem' }}>
|
|
Track two — Artist (selected)
|
|
</Row>
|
|
<Row style={{ padding: '0.5rem 0.75rem' }}>
|
|
Track three — Artist
|
|
</Row>
|
|
</List>
|
|
</Section>
|
|
|
|
<Section title="Table">
|
|
<Table>
|
|
<THead>
|
|
<Tr>
|
|
<Th>Title</Th>
|
|
<Th>Artist</Th>
|
|
<Th>Duration</Th>
|
|
</Tr>
|
|
</THead>
|
|
<TBody>
|
|
<Tr>
|
|
<Td>Intro</Td>
|
|
<Td>Aphex</Td>
|
|
<Td>2:14</Td>
|
|
</Tr>
|
|
<Tr selected>
|
|
<Td>Windowlicker</Td>
|
|
<Td>Aphex</Td>
|
|
<Td>6:07</Td>
|
|
</Tr>
|
|
<Tr>
|
|
<Td>Avril 14th</Td>
|
|
<Td>Aphex</Td>
|
|
<Td>2:01</Td>
|
|
</Tr>
|
|
</TBody>
|
|
</Table>
|
|
</Section>
|
|
|
|
<Section title="Menu, Dialog, AlertDialog">
|
|
<div style={rowWrap}>
|
|
<Menu>
|
|
<MenuTrigger asChild>
|
|
<Button variant="ghost">Open menu ▾</Button>
|
|
</MenuTrigger>
|
|
<MenuContent>
|
|
<MenuItem>Play</MenuItem>
|
|
<MenuItem shortcut="⌘N">Add to queue</MenuItem>
|
|
<MenuSeparator />
|
|
<MenuItem>Edit metadata</MenuItem>
|
|
</MenuContent>
|
|
</Menu>
|
|
|
|
<Dialog
|
|
trigger={<Button variant="primary">Open dialog</Button>}
|
|
title="Dialog title"
|
|
description="Composed from modern-sk primitives."
|
|
footer={
|
|
<DialogClose asChild>
|
|
<Button variant="primary">Done</Button>
|
|
</DialogClose>
|
|
}
|
|
>
|
|
<p
|
|
style={{
|
|
color: 'var(--color-text-2)',
|
|
fontSize: '0.875rem',
|
|
margin: 0,
|
|
}}
|
|
>
|
|
Dialog body content.
|
|
</p>
|
|
</Dialog>
|
|
|
|
<AlertDialog
|
|
trigger={<Button variant="ember">Delete…</Button>}
|
|
title="Delete track?"
|
|
description="This permanently removes the file from the server."
|
|
actionLabel="Delete"
|
|
destructive
|
|
onAction={() => undefined}
|
|
/>
|
|
</div>
|
|
</Section>
|
|
|
|
<Section title="Window">
|
|
<Window
|
|
title="Now Playing"
|
|
badge={
|
|
<Badge variant="lime" dot>
|
|
live
|
|
</Badge>
|
|
}
|
|
>
|
|
<p
|
|
style={{
|
|
color: 'var(--color-text-2)',
|
|
fontSize: '0.875rem',
|
|
margin: 0,
|
|
}}
|
|
>
|
|
Window chrome for grouped content.
|
|
</p>
|
|
</Window>
|
|
</Section>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|