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:
2026-05-30 23:14:34 +03:00
commit 01d41c2346
24 changed files with 7390 additions and 0 deletions
+567
View File
@@ -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 &amp; 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 &amp; 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 &amp; 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 youre
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;