Convert app into @modernsk/ui component library package
- Add library entry (src/index.ts) re-exporting all components, theme, and TooltipProvider - Add shippable stylesheet (src/styles/index.css): tokens + components only, font inlined as base64 at build time - Build with tsup (ESM + CJS + .d.ts) and esbuild for CSS - package.json: exports map, files, sideEffects, peerDependencies (react/react-dom), correct deps (radix-ui), prepare-on-install - Fix phantom dependency: declare radix-ui, drop unused @radix-ui/themes - Remove Storybook boilerplate, Tailwind/PostCSS (unused) - Keep App.tsx + Rsbuild as dev-only playground (not published) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+567
@@ -0,0 +1,567 @@
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
ArrowRight,
|
||||
Trash,
|
||||
Plus,
|
||||
DotsThree,
|
||||
MagnifyingGlass,
|
||||
Folder,
|
||||
FolderOpen,
|
||||
PencilSimple,
|
||||
Copy,
|
||||
Tag,
|
||||
Info,
|
||||
FilePdf,
|
||||
FileText,
|
||||
CheckCircle,
|
||||
Warning,
|
||||
WarningOctagon,
|
||||
} from '@phosphor-icons/react';
|
||||
import { useTheme } from './components/theme';
|
||||
import {
|
||||
AlertDialog,
|
||||
Badge,
|
||||
Button,
|
||||
Callout,
|
||||
Card,
|
||||
Checkbox,
|
||||
Chip,
|
||||
Control,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
IconButton,
|
||||
List,
|
||||
MenuRow,
|
||||
MenuSeparator,
|
||||
MenuSurface,
|
||||
Progress,
|
||||
RadioGroup,
|
||||
RadioItem,
|
||||
Row,
|
||||
ScrollArea,
|
||||
SearchField,
|
||||
SegmentedControl,
|
||||
Select,
|
||||
Slider,
|
||||
Spinner,
|
||||
Stepper,
|
||||
Switch,
|
||||
TBody,
|
||||
THead,
|
||||
Table,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
Td,
|
||||
TextArea,
|
||||
TextField,
|
||||
Th,
|
||||
Tooltip,
|
||||
Tr,
|
||||
Window,
|
||||
} from './components/ui';
|
||||
|
||||
const Section = ({ label, children }: { label: string; children: React.ReactNode }) => (
|
||||
<section>
|
||||
<div className="sec-label">{label}</div>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
|
||||
const App = () => {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const [seg, setSeg] = useState('list');
|
||||
const [tab, setTab] = useState('general');
|
||||
const [count, setCount] = useState(4);
|
||||
const [chips, setChips] = useState(['design', '2026', 'invoices']);
|
||||
|
||||
return (
|
||||
<div className="msk-felt">
|
||||
<div className="wrap">
|
||||
<header>
|
||||
<div className="topbar">
|
||||
<div>
|
||||
<div className="word">
|
||||
MODERN<b>SK</b> · KITCHEN SINK
|
||||
</div>
|
||||
<p className="sub">
|
||||
Every component, live and interactive, built on Radix Primitives
|
||||
styled from the global tokens in{' '}
|
||||
<span className="msk-mono">tokens.css</span> +{' '}
|
||||
<span className="msk-mono">components.css</span>. Click, toggle,
|
||||
focus — it all responds.
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ flexShrink: 0 }}>
|
||||
<SegmentedControl
|
||||
value={theme}
|
||||
onValueChange={(v) => setTheme(v as 'dark' | 'light')}
|
||||
items={[
|
||||
{ value: 'dark', label: 'Dark' },
|
||||
{ value: 'light', label: 'Light' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* BUTTONS */}
|
||||
<Section label="Buttons">
|
||||
<div className="stack">
|
||||
<div>
|
||||
<div className="cap">Variants</div>
|
||||
<div className="cluster">
|
||||
<Button variant="primary">
|
||||
<ArrowRight size={16} />
|
||||
Primary
|
||||
</Button>
|
||||
<Button>Push button</Button>
|
||||
<Button variant="ember">
|
||||
<Trash size={16} />
|
||||
Delete
|
||||
</Button>
|
||||
<Button variant="ghost">Cancel</Button>
|
||||
<Button disabled>Disabled</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="cap">Sizes & icon</div>
|
||||
<div className="cluster">
|
||||
<Button variant="primary" size="sm">
|
||||
Small
|
||||
</Button>
|
||||
<Button size="sm">Small</Button>
|
||||
<Button iconOnly aria-label="Add">
|
||||
<Plus size={16} />
|
||||
</Button>
|
||||
<Button iconOnly aria-label="More">
|
||||
<DotsThree size={16} weight="bold" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* FIELDS */}
|
||||
<Section label="Text fields & selects">
|
||||
<div className="two">
|
||||
<div className="stack">
|
||||
<div>
|
||||
<div className="lab">Name</div>
|
||||
<TextField defaultValue="Quarterly Report" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="lab">Location</div>
|
||||
<TextField placeholder="Choose a folder…" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="lab">Search</div>
|
||||
<SearchField
|
||||
icon={<MagnifyingGlass size={16} />}
|
||||
placeholder="Search files…"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack">
|
||||
<div>
|
||||
<div className="lab">View</div>
|
||||
<Select
|
||||
defaultValue="name"
|
||||
aria-label="Sort order"
|
||||
items={[
|
||||
{ value: 'name', label: 'Sort by name' },
|
||||
{ value: 'date', label: 'Sort by date' },
|
||||
{ value: 'size', label: 'Sort by size' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="lab">Notes</div>
|
||||
<TextArea
|
||||
placeholder="Add a note…"
|
||||
defaultValue="Everything right at your hands."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* TOGGLES */}
|
||||
<Section label="Switches · checkboxes · radios">
|
||||
<div className="three">
|
||||
<div>
|
||||
<div className="cap">Switch</div>
|
||||
<div className="stack">
|
||||
<Control control={<Switch defaultChecked />}>
|
||||
Sync across devices
|
||||
</Control>
|
||||
<Control control={<Switch />}>Show hidden files</Control>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="cap">Checkbox</div>
|
||||
<div className="stack">
|
||||
<Control control={<Checkbox defaultChecked />}>
|
||||
Include subfolders
|
||||
</Control>
|
||||
<Control control={<Checkbox />}>Follow symlinks</Control>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="cap">Radio</div>
|
||||
<RadioGroup defaultValue="list" className="stack">
|
||||
<Control control={<RadioItem value="list" />}>List view</Control>
|
||||
<Control control={<RadioItem value="grid" />}>Grid view</Control>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* CONTROLS */}
|
||||
<Section label="Segmented · slider · stepper · tabs · progress">
|
||||
<div className="two">
|
||||
<div className="stack">
|
||||
<div>
|
||||
<div className="cap">Segmented</div>
|
||||
<SegmentedControl
|
||||
value={seg}
|
||||
onValueChange={setSeg}
|
||||
items={[
|
||||
{ value: 'icons', label: 'Icons' },
|
||||
{ value: 'list', label: 'List' },
|
||||
{ value: 'columns', label: 'Columns' },
|
||||
{ value: 'gallery', label: 'Gallery' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="cap">Slider & stepper</div>
|
||||
<div className="cluster">
|
||||
<Slider defaultValue={[62]} max={100} step={1} />
|
||||
<Stepper
|
||||
onDecrement={() => setCount((n) => Math.max(0, n - 1))}
|
||||
onIncrement={() => setCount((n) => n + 1)}
|
||||
/>
|
||||
<span className="msk-mono" style={{ color: 'var(--fg-2)' }}>
|
||||
{count}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack">
|
||||
<div>
|
||||
<div className="cap">Tabs</div>
|
||||
<Tabs value={tab} onValueChange={setTab}>
|
||||
<TabsList
|
||||
items={[
|
||||
{ value: 'general', label: 'General' },
|
||||
{ value: 'sharing', label: 'Sharing' },
|
||||
{ value: 'tags', label: 'Tags' },
|
||||
]}
|
||||
/>
|
||||
<TabsContent value={tab} />
|
||||
</Tabs>
|
||||
</div>
|
||||
<div>
|
||||
<div className="cap">Progress</div>
|
||||
<div style={{ marginTop: 6 }}>
|
||||
<Progress value={64} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* BADGES */}
|
||||
<Section label="Badges · chips · tags">
|
||||
<div className="stack">
|
||||
<div className="cluster">
|
||||
<Badge variant="lime">Synced</Badge>
|
||||
<Badge variant="ember">3 conflicts</Badge>
|
||||
<Badge variant="neutral">Draft</Badge>
|
||||
<Badge variant="outline">v2.4</Badge>
|
||||
<Badge variant="neutral" dot style={{ color: 'var(--lime)' }}>
|
||||
Online
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="cluster">
|
||||
{chips.map((c) => (
|
||||
<Chip
|
||||
key={c}
|
||||
onRemove={() => setChips((cs) => cs.filter((x) => x !== c))}
|
||||
>
|
||||
{c}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* SURFACES */}
|
||||
<Section label="Cards · list rows · menu">
|
||||
<div className="three">
|
||||
<Card>
|
||||
<div style={{ fontSize: 24, color: 'var(--lime)' }}>
|
||||
<Folder weight="fill" />
|
||||
</div>
|
||||
<div style={{ fontWeight: 600, marginTop: 10 }}>Projects</div>
|
||||
<div
|
||||
style={{ color: 'var(--fg-3)', fontSize: 13, marginTop: 2 }}
|
||||
>
|
||||
24 items · 1.2 GB
|
||||
</div>
|
||||
<div style={{ marginTop: 14 }}>
|
||||
<Progress value={48} />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<List>
|
||||
<Row selected>
|
||||
<Folder weight="fill" size={22} color="#bef264" />
|
||||
<span className="nm">Projects</span>
|
||||
<span className="meta">24</span>
|
||||
</Row>
|
||||
<Row>
|
||||
<Folder weight="fill" size={22} color="#e9572b" />
|
||||
<span className="nm">Invoices</span>
|
||||
<span className="meta">8</span>
|
||||
</Row>
|
||||
<Row>
|
||||
<FilePdf weight="fill" size={22} color="var(--fg-2)" />
|
||||
<span className="nm">Contract.pdf</span>
|
||||
<span className="meta">2.4 MB</span>
|
||||
</Row>
|
||||
<Row>
|
||||
<FileText weight="fill" size={22} color="var(--fg-2)" />
|
||||
<span className="nm">notes.md</span>
|
||||
<span className="meta">12 KB</span>
|
||||
</Row>
|
||||
</List>
|
||||
|
||||
<MenuSurface>
|
||||
<MenuRow icon={<FolderOpen size={16} />} shortcut="⌘O">
|
||||
Open
|
||||
</MenuRow>
|
||||
<MenuRow icon={<PencilSimple size={16} />} shortcut="⏎">
|
||||
Rename
|
||||
</MenuRow>
|
||||
<MenuRow icon={<Copy size={16} />} shortcut="⌘D">
|
||||
Duplicate
|
||||
</MenuRow>
|
||||
<MenuSeparator />
|
||||
<MenuRow icon={<Tag size={16} />}>Add Tag…</MenuRow>
|
||||
<MenuRow icon={<Info size={16} />} shortcut="⌘I">
|
||||
Get Info
|
||||
</MenuRow>
|
||||
</MenuSurface>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* WINDOW */}
|
||||
<Section label="Window — components composed">
|
||||
<Window
|
||||
title="New Bookmark"
|
||||
style={{ maxWidth: 600 }}
|
||||
badge={
|
||||
<Badge variant="neutral" dot style={{ color: 'var(--lime)' }}>
|
||||
Saved
|
||||
</Badge>
|
||||
}
|
||||
>
|
||||
<div className="panel">
|
||||
<div className="stack">
|
||||
<div>
|
||||
<div className="lab">Name</div>
|
||||
<TextField defaultValue="Projects" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="lab">Location</div>
|
||||
<SearchField
|
||||
icon={<Folder size={16} />}
|
||||
defaultValue="~/Documents/2026"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginTop: 2 }}>
|
||||
<Control control={<Switch defaultChecked />}>
|
||||
Sync across devices
|
||||
</Control>
|
||||
</div>
|
||||
<div
|
||||
className="cluster"
|
||||
style={{
|
||||
justifyContent: 'flex-end',
|
||||
marginTop: 6,
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<Button variant="ghost">Cancel</Button>
|
||||
<Button variant="primary">Add Bookmark</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Window>
|
||||
<div style={{ marginTop: 14 }}>
|
||||
<Tooltip
|
||||
content={
|
||||
<>
|
||||
<Info
|
||||
size={14}
|
||||
style={{ marginRight: 5, color: 'var(--lime)' }}
|
||||
/>
|
||||
Tooltip — appears over floating layers
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button variant="ghost" size="sm">
|
||||
Hover for tooltip
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* ICON BUTTONS · SPINNER */}
|
||||
<Section label="Icon buttons · spinner">
|
||||
<div className="cluster">
|
||||
<IconButton variant="primary" aria-label="Add">
|
||||
<Plus size={16} weight="bold" />
|
||||
</IconButton>
|
||||
<IconButton aria-label="Edit">
|
||||
<PencilSimple size={15} />
|
||||
</IconButton>
|
||||
<IconButton variant="ember" aria-label="Delete">
|
||||
<Trash size={15} />
|
||||
</IconButton>
|
||||
<IconButton variant="ghost" aria-label="More">
|
||||
<DotsThree size={18} weight="bold" />
|
||||
</IconButton>
|
||||
<IconButton size="lg" aria-label="Open">
|
||||
<FolderOpen size={18} />
|
||||
</IconButton>
|
||||
<span style={{ width: 16 }} />
|
||||
<Spinner size="sm" />
|
||||
<Spinner />
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* CALLOUTS */}
|
||||
<Section label="Callouts">
|
||||
<div className="stack">
|
||||
<Callout variant="info" icon={<Info size={17} weight="fill" />}>
|
||||
<strong>Heads up.</strong> Files sync automatically when you’re
|
||||
online — no manual save needed.
|
||||
</Callout>
|
||||
<Callout variant="success" icon={<CheckCircle size={17} weight="fill" />}>
|
||||
<strong>All set.</strong> Your 24 projects are backed up and
|
||||
encrypted.
|
||||
</Callout>
|
||||
<Callout variant="warning" icon={<Warning size={17} weight="fill" />}>
|
||||
<strong>Low space.</strong> 1.2 GB left on this device.
|
||||
</Callout>
|
||||
<Callout variant="danger" icon={<WarningOctagon size={17} weight="fill" />}>
|
||||
<strong>3 conflicts.</strong> Some files changed in two places at
|
||||
once.
|
||||
</Callout>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* TABLE */}
|
||||
<Section label="Table">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Owner</Th>
|
||||
<Th>Status</Th>
|
||||
<Th style={{ textAlign: 'right' }}>Size</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
<Tr selected>
|
||||
<Td>Quarterly Report.pdf</Td>
|
||||
<Td className="muted">You</Td>
|
||||
<Td>
|
||||
<Badge variant="lime">Synced</Badge>
|
||||
</Td>
|
||||
<Td className="num">2.4 MB</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>Invoices</Td>
|
||||
<Td className="muted">Mara K.</Td>
|
||||
<Td>
|
||||
<Badge variant="ember">3 conflicts</Badge>
|
||||
</Td>
|
||||
<Td className="num">812 KB</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>notes.md</Td>
|
||||
<Td className="muted">You</Td>
|
||||
<Td>
|
||||
<Badge variant="neutral">Draft</Badge>
|
||||
</Td>
|
||||
<Td className="num">12 KB</Td>
|
||||
</Tr>
|
||||
</TBody>
|
||||
</Table>
|
||||
</Section>
|
||||
|
||||
{/* SCROLL AREA */}
|
||||
<Section label="Scroll area">
|
||||
<Card style={{ padding: 0, maxWidth: 320 }}>
|
||||
<ScrollArea style={{ height: 160 }}>
|
||||
<List style={{ border: 'none', boxShadow: 'none', borderRadius: 0 }}>
|
||||
{Array.from({ length: 12 }).map((_, i) => (
|
||||
<Row key={i}>
|
||||
<FileText weight="fill" size={20} color="var(--fg-2)" />
|
||||
<span className="nm">document-{i + 1}.txt</span>
|
||||
<span className="meta">{(i + 1) * 7} KB</span>
|
||||
</Row>
|
||||
))}
|
||||
</List>
|
||||
</ScrollArea>
|
||||
</Card>
|
||||
</Section>
|
||||
|
||||
{/* DIALOGS */}
|
||||
<Section label="Dialog · alert dialog">
|
||||
<div className="cluster">
|
||||
<Dialog
|
||||
title="Rename file"
|
||||
description="Choose a new name for this item."
|
||||
trigger={<Button variant="primary">Open dialog</Button>}
|
||||
footer={
|
||||
<>
|
||||
<DialogClose asChild>
|
||||
<Button variant="ghost">Cancel</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button variant="primary">Save</Button>
|
||||
</DialogClose>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="lab">Name</div>
|
||||
<TextField defaultValue="Quarterly Report.pdf" autoFocus />
|
||||
</Dialog>
|
||||
|
||||
<AlertDialog
|
||||
title="Delete 3 files?"
|
||||
description="This permanently removes the selected files. This action cannot be undone."
|
||||
cancelLabel="Keep files"
|
||||
actionLabel="Delete"
|
||||
destructive
|
||||
trigger={
|
||||
<Button variant="ember">
|
||||
<Trash size={16} />
|
||||
Delete…
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user