diff --git a/apps/web/src/components/ui/ButtonLoading.stories.tsx b/apps/web/src/components/ui/ButtonLoading.stories.tsx new file mode 100644 index 000000000..0dfeccb03 --- /dev/null +++ b/apps/web/src/components/ui/ButtonLoading.stories.tsx @@ -0,0 +1,66 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ButtonLoading } from './button-loading'; + +const meta: Meta = { + title: 'UI/ButtonLoading', + component: ButtonLoading, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'], + }, + size: { control: 'select', options: ['default', 'sm', 'lg', 'icon'] }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + children: 'Submit', + isLoading: false, + }, +}; + +export const Loading: Story = { + args: { + children: 'Submit', + isLoading: true, + }, +}; + +export const LoadingWithText: Story = { + args: { + children: 'Save Changes', + isLoading: true, + loadingText: 'Saving...', + }, +}; + +export const Destructive: Story = { + args: { + children: 'Delete', + variant: 'destructive', + isLoading: true, + loadingText: 'Deleting...', + }, +}; + +export const Outline: Story = { + args: { + children: 'Export', + variant: 'outline', + isLoading: true, + loadingText: 'Exporting...', + }, +}; + +export const Small: Story = { + args: { + children: 'Upload', + size: 'sm', + isLoading: true, + }, +}; diff --git a/apps/web/src/components/ui/ContentFadeIn.stories.tsx b/apps/web/src/components/ui/ContentFadeIn.stories.tsx new file mode 100644 index 000000000..437eb30ce --- /dev/null +++ b/apps/web/src/components/ui/ContentFadeIn.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ContentFadeIn } from './ContentFadeIn'; + +const meta: Meta = { + title: 'UI/ContentFadeIn', + component: ContentFadeIn, + tags: ['autodocs'], +}; +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + children: ( +
+

Content loaded

+

+ This content fades in with a smooth 200ms transition using SUMI easing. +

+
+ ), + }, +}; + +export const Card: Story = { + args: { + children: ( +
+
+

Track Title

+

Artist Name

+
+ ), + className: 'max-w-sm', + }, +}; diff --git a/apps/web/src/components/ui/DesignTokens.stories.tsx b/apps/web/src/components/ui/DesignTokens.stories.tsx new file mode 100644 index 000000000..4f331f0e8 --- /dev/null +++ b/apps/web/src/components/ui/DesignTokens.stories.tsx @@ -0,0 +1,143 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +/** + * SUMI Design Tokens — Visual reference for the design system. + * Shows all color pigments, typography, spacing, and motion tokens. + */ + +function ColorSwatch({ name, value, cssVar }: { name: string; value: string; cssVar: string }) { + return ( +
+
+
+
{name}
+
{cssVar}
+
+
+ ); +} + +function TokenSection({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +function DesignTokensShowcase() { + return ( +
+
+

SUMI Design System v2.0

+

"L'encre et la lumière" — Ink and Light

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Typography

+
+

+ Heading — Space Grotesk (4xl) +

+

+ Heading — Space Grotesk (2xl) +

+

+ Body — Inter (base, 14px) +

+

+ Mono — JetBrains Mono (sm) +

+
+
+ +
+

Spacing

+
+ {[ + { name: 'space-1', size: '4px' }, + { name: 'space-2', size: '8px' }, + { name: 'space-3', size: '12px' }, + { name: 'space-4', size: '16px' }, + { name: 'space-6', size: '24px' }, + { name: 'space-8', size: '32px' }, + ].map(({ name, size }) => ( +
+
+ + --sumi-{name} ({size}) + +
+ ))} +
+
+ +
+

Border Radius

+
+ {[ + { name: 'xs', size: '2px' }, + { name: 'sm', size: '4px' }, + { name: 'md', size: '6px' }, + { name: 'lg', size: '12px' }, + { name: 'xl', size: '16px' }, + { name: 'full', size: '9999px' }, + ].map(({ name, size }) => ( +
+
+ {name} +
+ ))} +
+
+
+ ); +} + +const meta: Meta = { + title: 'Design System/Tokens', + tags: ['autodocs'], +}; +export default meta; + +type Story = StoryObj; + +export const AllTokens: Story = { + render: () => , +}; diff --git a/apps/web/src/components/ui/EmptyState.stories.tsx b/apps/web/src/components/ui/EmptyState.stories.tsx new file mode 100644 index 000000000..2acb32307 --- /dev/null +++ b/apps/web/src/components/ui/EmptyState.stories.tsx @@ -0,0 +1,99 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { EmptyState } from './empty-state'; +import { Inbox, Search, Music, Users } from 'lucide-react'; + +const meta: Meta = { + title: 'UI/EmptyState', + component: EmptyState, + tags: ['autodocs'], + argTypes: { + size: { control: 'select', options: ['sm', 'md', 'lg'] }, + variant: { control: 'select', options: ['default', 'centered', 'card'] }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + title: 'No items found', + description: 'There are no items to display yet.', + }, +}; + +export const WithIcon: Story = { + args: { + icon: , + title: 'Your inbox is empty', + description: 'New messages will appear here when you receive them.', + }, +}; + +export const WithAction: Story = { + args: { + icon: , + title: 'No tracks uploaded', + description: 'Upload your first track to get started.', + action: { + label: 'Upload a track', + onClick: () => {}, + }, + }, +}; + +export const SearchEmpty: Story = { + args: { + icon: , + title: 'No results found', + description: 'Try adjusting your search criteria or browse by genre.', + action: { + label: 'Clear search', + onClick: () => {}, + variant: 'outline', + }, + }, +}; + +export const Small: Story = { + args: { + size: 'sm', + title: 'No followers yet', + icon: , + }, +}; + +export const Large: Story = { + args: { + size: 'lg', + icon: , + title: 'Your library is empty', + description: 'Discover music and add it to your library.', + action: { label: 'Explore', onClick: () => {} }, + }, +}; + +export const CardVariant: Story = { + args: { + variant: 'card', + icon: , + title: 'Drop files here', + description: 'Drag and drop files to upload.', + }, +}; + +export const CenteredVariant: Story = { + args: { + variant: 'centered', + icon: , + title: 'Nothing playing', + description: 'Select a track to start listening.', + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; diff --git a/packages/design-system/README.md b/packages/design-system/README.md index 0f951567a..c06befabe 100644 --- a/packages/design-system/README.md +++ b/packages/design-system/README.md @@ -1,10 +1,87 @@ # @veza/design-system -**État v0.941** : Package sous-utilisé. +**SUMI Design System v2.0** — "L'encre et la lumière" (Ink and Light) -- Les tokens et primitives UI sont principalement définis dans `apps/web/src/index.css` et `DESIGN_TOKENS.md`. -- Les composants de l'app utilisent `apps/web/src/components/ui/` (shadcn/ui, etc.). -- Ce package fournit `dist/` (Button, Input, etc.) pour la route `/design-system` (démo visuelle). -- **Recommandation** : Conserver pour la démo ; migrer vers `apps/web` si besoin de composants partagés. +The centralized design system for the Veza platform. Provides design tokens, component type registry, and utilities. -Voir `docs/FEATURE_STATUS.md` et `apps/web/docs/DESIGN_TOKENS.md`. +## Structure + +``` +packages/design-system/ +├── src/ +│ ├── index.ts # Barrel exports +│ ├── utils.ts # cn() utility +│ ├── tokens/ +│ │ ├── index.ts # All token exports +│ │ ├── colors.ts # Background, surface, text, pigment, semantic colors +│ │ ├── typography.ts # Font families, sizes, weights, line heights +│ │ ├── spacing.ts # Spacing scale, border radius, z-index, layout +│ │ └── motion.ts # Duration and easing tokens +│ └── components/ +│ └── index.ts # Component type registry +└── package.json +``` + +## Usage + +### Design Tokens (TypeScript) + +```typescript +import { pigments, fontFamilies, spacing } from '@veza/design-system/tokens'; + +// Colors +pigments.accent.base // '#7c9dd6' +pigments.vermillion.base // '#d4634a' + +// Typography +fontFamilies.heading // "'Space Grotesk', 'Inter', sans-serif" + +// Spacing +spacing['4'] // '16px' +``` + +### Design Tokens (CSS) + +The CSS custom properties are the primary token interface, defined in `apps/web/src/index.css`: + +```css +color: var(--sumi-accent); +padding: var(--sumi-space-4); +font-family: var(--sumi-font-heading); +border-radius: var(--sumi-radius-md); +``` + +### Components + +Components are implemented in `apps/web/src/components/ui/` and imported via path alias: + +```typescript +import { Button } from '@/components/ui/button'; +import { Card } from '@/components/ui/card'; +import { Dialog } from '@/components/ui/dialog'; +``` + +See `apps/web/.storybook/` for Storybook documentation of all components. + +## Themes + +- **Dark** (default) — Ink on void +- **Light** — Washi paper aesthetic (`[data-theme="light"]`) +- **High Contrast** — WCAG AA 4.5:1+ (`[data-contrast="high"]`) +- **Compact Density** — Reduced spacing (`[data-density="compact"]`) + +## Color System — The 4 Pigments + +| Pigment | Hex | Usage | +|---------|-----|-------| +| **Accent** (Indigo) | `#7c9dd6` | Primary actions, links, focus | +| **Vermillion** | `#d4634a` | Errors, destructive, live | +| **Sage** | `#7a9e6c` | Success, online | +| **Gold** | `#c9a84c` | Warnings, achievements | + +## References + +- Design tokens source: `apps/web/src/index.css` +- Token documentation: `apps/web/docs/DESIGN_TOKENS.md` +- Storybook: `apps/web/.storybook/` +- Component source: `apps/web/src/components/ui/` diff --git a/packages/design-system/package.json b/packages/design-system/package.json new file mode 100644 index 000000000..a91e3a37b --- /dev/null +++ b/packages/design-system/package.json @@ -0,0 +1,34 @@ +{ + "name": "@veza/design-system", + "version": "2.0.0", + "description": "SUMI Design System — Design tokens, utilities, and component re-exports for the Veza platform", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts", + "./tokens": "./src/tokens/index.ts", + "./tokens/colors": "./src/tokens/colors.ts", + "./tokens/typography": "./src/tokens/typography.ts", + "./tokens/spacing": "./src/tokens/spacing.ts", + "./tokens/motion": "./src/tokens/motion.ts" + }, + "files": [ + "src/" + ], + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "clsx": "^2.0.0", + "tailwind-merge": "^3.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "devDependencies": { + "typescript": "^5.9.0" + }, + "license": "UNLICENSED" +} diff --git a/packages/design-system/src/components/index.ts b/packages/design-system/src/components/index.ts new file mode 100644 index 000000000..fc1346d3a --- /dev/null +++ b/packages/design-system/src/components/index.ts @@ -0,0 +1,116 @@ +/** + * SUMI Design System v2.0 — Component Registry + * + * This file documents the canonical component set of the SUMI design system. + * Components are implemented in apps/web/src/components/ui/ and imported + * from there using the @/components/ui/ path alias. + * + * To use in the web app: + * import { Button } from '@/components/ui/button'; + * import { Card } from '@/components/ui/card'; + * + * This registry exists for documentation and type-checking purposes. + */ + +/** + * SUMI Component Categories: + * + * ═══ PRIMITIVES ═══ + * Button — Primary action element (variants: default, destructive, outline, secondary, ghost, link) + * Input — Text input field + * Textarea — Multi-line text input + * Label — Form field label + * Checkbox — Binary selection + * RadioGroup — Single selection from options + * Switch — Toggle control + * Slider — Range input + * Select — Dropdown selection + * + * ═══ LAYOUT ═══ + * Card — Content container with border + * Accordion — Collapsible content sections + * Tabs — Tabbed content panels + * Sidebar — Navigation sidebar + * ScrollArea — Custom scrollable container + * Table — Data table + * + * ═══ FEEDBACK ═══ + * Alert — Informational message + * Badge — Status indicator + * Dialog — Modal dialog + * Toast — Temporary notification + * Tooltip — Hover information + * HoverCard — Rich hover popup + * Skeleton — Loading placeholder + * Progress — Progress indicator + * LoadingSpinner — Animated spinner + * + * ═══ NAVIGATION ═══ + * DropdownMenu — Contextual menu + * ContextMenu — Right-click menu + * NavigationProgress — Page transition indicator + * + * ═══ SPECIALIZED ═══ + * FileUpload — File upload with drag & drop + * AvatarUpload — Avatar image upload with cropper + * DatePicker — Date selection + * VirtualizedList — Virtualized scrolling for large lists + * OptimizedImage — Responsive image with blur placeholder + * AnimatedNumber — Animated numeric display + */ + +// Component type exports for type-safety when referencing SUMI components +export type SumiComponentName = + // Primitives + | 'Button' + | 'Input' + | 'Textarea' + | 'Label' + | 'Checkbox' + | 'RadioGroup' + | 'Switch' + | 'Slider' + | 'Select' + | 'FloatingInput' + // Layout + | 'Card' + | 'Accordion' + | 'Tabs' + | 'Sidebar' + | 'ScrollArea' + | 'Table' + | 'Collapsible' + // Feedback + | 'Alert' + | 'Badge' + | 'Dialog' + | 'ConfirmationDialog' + | 'Toast' + | 'Tooltip' + | 'HoverCard' + | 'Skeleton' + | 'Progress' + | 'LoadingSpinner' + | 'LoadingState' + | 'ErrorBoundary' + | 'ErrorDisplay' + // Navigation + | 'DropdownMenu' + | 'ContextMenu' + | 'NavigationProgress' + | 'ScrollToTop' + // Specialized + | 'Avatar' + | 'AvatarUpload' + | 'FileUpload' + | 'DatePicker' + | 'ImageCropper' + | 'VirtualizedList' + | 'OptimizedImage' + | 'AnimatedNumber' + | 'DataList' + | 'FormField' + | 'FAB' + | 'FocusTrap' + | 'KeyboardShortcutsPanel' + | 'WaveformVisualizer'; diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts new file mode 100644 index 000000000..a18ecde30 --- /dev/null +++ b/packages/design-system/src/index.ts @@ -0,0 +1,52 @@ +/** + * @veza/design-system — SUMI Design System v2.0 + * "L'encre et la lumière" — Ink and Light + * + * This package provides: + * - Design tokens (colors, typography, spacing, motion) as TypeScript objects + * - Component type registry for the SUMI component set + * - Utility functions (cn) + * + * Components are implemented in apps/web/src/components/ui/ and should be + * imported from there using the @/components/ui/ path alias. + * + * Usage: + * import { colors, typography } from '@veza/design-system/tokens'; + * import { pigments } from '@veza/design-system/tokens/colors'; + */ + +// ═══ Design Tokens ═══ +export { + colors, + backgrounds, + surfaces, + borders, + text, + pigments, + semantic, + glass, + shadows, + lightTheme, + typography, + fontFamilies, + fontSizes, + lineHeights, + letterSpacings, + fontWeights, + spacingTokens, + spacing, + radius, + zIndex, + layout, + motion, + durations, + easings, +} from './tokens'; + +export type { SumiColor } from './tokens'; + +// ═══ Component Registry ═══ +export type { SumiComponentName } from './components'; + +// ═══ Utilities ═══ +export { cn } from './utils'; diff --git a/packages/design-system/src/tokens/colors.ts b/packages/design-system/src/tokens/colors.ts new file mode 100644 index 000000000..f41c17208 --- /dev/null +++ b/packages/design-system/src/tokens/colors.ts @@ -0,0 +1,157 @@ +/** + * SUMI Design System v2.0 — Color Tokens + * "L'encre et la lumière" — Ink and Light + * + * Source of truth: apps/web/src/index.css + * These tokens mirror the CSS custom properties for use in TypeScript. + */ + +// ═══ DARK THEME (default) ═══ + +export const backgrounds = { + void: '#0c0c0f', + base: '#121215', + raised: '#1a1a1f', + overlay: '#222228', + hover: '#2a2a31', + active: '#32323a', + wash: '#18181d', +} as const; + +export const surfaces = { + inset: '#101013', + subtle: '#1e1e24', + card: '#1a1a1f', + elevated: '#242430', +} as const; + +export const borders = { + faint: 'rgba(255,255,255, 0.06)', + default: 'rgba(255,255,255, 0.10)', + strong: 'rgba(255,255,255, 0.16)', + focus: 'rgba(139,170,220, 0.50)', + accent: 'rgba(139,170,220, 0.30)', +} as const; + +export const text = { + primary: '#f0ede8', + secondary: '#a8a4a0', + tertiary: '#706c68', + disabled: '#4a4844', + inverse: '#121215', + link: '#8baade', +} as const; + +// ═══ PIGMENTS — The 4 accent pigments ═══ + +export const pigments = { + accent: { + base: '#7c9dd6', + hover: '#93afe0', + active: '#6b8dc6', + muted: 'rgba(124,157,214, 0.20)', + subtle: 'rgba(124,157,214, 0.12)', + emphasis: '#5a7fba', + }, + vermillion: { + base: '#d4634a', + hover: '#de7a64', + subtle: 'rgba(212,99,74, 0.12)', + }, + sage: { + base: '#7a9e6c', + hover: '#8eb280', + subtle: 'rgba(122,158,108, 0.12)', + }, + gold: { + base: '#c9a84c', + hover: '#d6b860', + subtle: 'rgba(201,168,76, 0.12)', + }, +} as const; + +// ═══ SEMANTIC ═══ + +export const semantic = { + success: pigments.sage.base, + successSubtle: pigments.sage.subtle, + warning: pigments.gold.base, + warningSubtle: pigments.gold.subtle, + error: pigments.vermillion.base, + errorSubtle: pigments.vermillion.subtle, + info: pigments.accent.base, + live: '#e05a5a', + online: pigments.sage.base, +} as const; + +// ═══ GLASS ═══ + +export const glass = { + bg: 'rgba(18,18,21, 0.80)', + border: 'rgba(255,255,255, 0.08)', + blur: '12px', +} as const; + +// ═══ SHADOWS ═══ + +export const shadows = { + xs: '0 1px 2px rgba(0,0,0,0.30)', + sm: '0 2px 4px rgba(0,0,0,0.25), 0 1px 2px rgba(0,0,0,0.20)', + md: '0 4px 12px rgba(0,0,0,0.30), 0 2px 4px rgba(0,0,0,0.15)', + lg: '0 8px 24px rgba(0,0,0,0.35), 0 4px 8px rgba(0,0,0,0.20)', + xl: '0 16px 48px rgba(0,0,0,0.40), 0 8px 16px rgba(0,0,0,0.20)', + '2xl': '0 24px 64px rgba(0,0,0,0.50)', + glow: '0 0 0 3px rgba(124,157,214,0.25)', + glowLg: '0 0 20px rgba(124,157,214,0.15)', +} as const; + +// ═══ LIGHT THEME ═══ + +export const lightTheme = { + backgrounds: { + void: '#f5f2ed', + base: '#faf8f5', + raised: '#ffffff', + overlay: '#f0ede8', + hover: '#e8e4df', + active: '#ddd9d3', + wash: '#f7f5f0', + }, + surfaces: { + inset: '#ede9e4', + subtle: '#f5f2ed', + card: '#ffffff', + elevated: '#ffffff', + }, + borders: { + faint: 'rgba(0,0,0, 0.06)', + default: 'rgba(0,0,0, 0.10)', + strong: 'rgba(0,0,0, 0.18)', + focus: 'rgba(90,127,186, 0.50)', + accent: 'rgba(90,127,186, 0.25)', + }, + text: { + primary: '#1a1a1f', + secondary: '#5c5854', + tertiary: '#8a8680', + disabled: '#b8b4b0', + inverse: '#f0ede8', + link: '#5a7fba', + }, +} as const; + +// ═══ ALL COLORS ═══ + +export const colors = { + backgrounds, + surfaces, + borders, + text, + pigments, + semantic, + glass, + shadows, + lightTheme, +} as const; + +export type SumiColor = typeof colors; diff --git a/packages/design-system/src/tokens/index.ts b/packages/design-system/src/tokens/index.ts new file mode 100644 index 000000000..a66cb086e --- /dev/null +++ b/packages/design-system/src/tokens/index.ts @@ -0,0 +1,21 @@ +/** + * SUMI Design System v2.0 — Design Tokens + * + * Centralized design tokens extracted from CSS custom properties. + * Use these for TypeScript-level access to the design system values. + * + * For CSS usage, prefer the CSS custom properties directly: + * var(--sumi-accent), var(--sumi-space-4), etc. + * + * For JS/TS usage (e.g., inline styles, canvas, SVG): + * import { colors, typography, spacing, motion } from '@veza/design-system/tokens'; + */ + +export { colors, backgrounds, surfaces, borders, text, pigments, semantic, glass, shadows, lightTheme } from './colors'; +export type { SumiColor } from './colors'; + +export { typography, fontFamilies, fontSizes, lineHeights, letterSpacings, fontWeights } from './typography'; + +export { spacingTokens, spacing, radius, zIndex, layout } from './spacing'; + +export { motion, durations, easings } from './motion'; diff --git a/packages/design-system/src/tokens/motion.ts b/packages/design-system/src/tokens/motion.ts new file mode 100644 index 000000000..1edb60ebb --- /dev/null +++ b/packages/design-system/src/tokens/motion.ts @@ -0,0 +1,27 @@ +/** + * SUMI Design System v2.0 — Motion & Animation Tokens + * + * Source of truth: apps/web/src/index.css + */ + +export const durations = { + instant: '75ms', + fast: '150ms', + normal: '200ms', + slow: '300ms', + slower: '500ms', +} as const; + +export const easings = { + default: 'cubic-bezier(0.25, 0.1, 0.25, 1)', + out: 'cubic-bezier(0.33, 1, 0.68, 1)', + in: 'cubic-bezier(0.32, 0, 0.67, 0)', + inOut: 'cubic-bezier(0.65, 0, 0.35, 1)', + bounce: 'cubic-bezier(0.34, 1.56, 0.64, 1)', + spring: 'cubic-bezier(0.175, 0.885, 0.32, 1.1)', +} as const; + +export const motion = { + durations, + easings, +} as const; diff --git a/packages/design-system/src/tokens/spacing.ts b/packages/design-system/src/tokens/spacing.ts new file mode 100644 index 000000000..fdf710f79 --- /dev/null +++ b/packages/design-system/src/tokens/spacing.ts @@ -0,0 +1,57 @@ +/** + * SUMI Design System v2.0 — Spacing & Layout Tokens + * + * Source of truth: apps/web/src/index.css + */ + +export const spacing = { + '0.5': '2px', + '1': '4px', + '1.5': '6px', + '2': '8px', + '2.5': '10px', + '3': '12px', + '4': '16px', + '5': '20px', + '6': '24px', + '8': '32px', + '10': '40px', + '12': '48px', + '16': '64px', + '20': '80px', +} as const; + +export const radius = { + xs: '2px', + sm: '4px', + md: '6px', + lg: '12px', + xl: '16px', + '2xl': '20px', + full: '9999px', +} as const; + +export const zIndex = { + base: 0, + raised: 10, + dropdown: 100, + sticky: 200, + overlay: 300, + modal: 400, + popover: 500, + toast: 600, + tooltip: 700, + max: 999, +} as const; + +export const layout = { + maxWidth: '1400px', + maxWidthContent: '1200px', +} as const; + +export const spacingTokens = { + spacing, + radius, + zIndex, + layout, +} as const; diff --git a/packages/design-system/src/tokens/typography.ts b/packages/design-system/src/tokens/typography.ts new file mode 100644 index 000000000..83c4c9dc5 --- /dev/null +++ b/packages/design-system/src/tokens/typography.ts @@ -0,0 +1,58 @@ +/** + * SUMI Design System v2.0 — Typography Tokens + * + * Source of truth: apps/web/src/index.css + */ + +export const fontFamilies = { + body: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", + heading: "'Space Grotesk', 'Inter', sans-serif", + mono: "'JetBrains Mono', 'SF Mono', 'Consolas', monospace", + serif: "'Noto Serif JP', Georgia, serif", +} as const; + +export const fontSizes = { + '4xl': '2.25rem', // 36px + '3xl': '1.875rem', // 30px + '2xl': '1.5rem', // 24px + xl: '1.25rem', // 20px + lg: '1.125rem', // 18px + md: '1rem', // 16px + base: '0.875rem', // 14px + sm: '0.8125rem', // 13px + xs: '0.75rem', // 12px +} as const; + +export const lineHeights = { + none: '1', + tight: '1.25', + snug: '1.375', + normal: '1.5', + relaxed: '1.625', + loose: '1.75', +} as const; + +export const letterSpacings = { + tighter: '-0.03em', + tight: '-0.015em', + normal: '0', + wide: '0.025em', + wider: '0.05em', + widest: '0.1em', +} as const; + +export const fontWeights = { + light: 300, + regular: 400, + medium: 500, + semibold: 600, + bold: 700, +} as const; + +export const typography = { + fontFamilies, + fontSizes, + lineHeights, + letterSpacings, + fontWeights, +} as const; diff --git a/packages/design-system/src/utils.ts b/packages/design-system/src/utils.ts new file mode 100644 index 000000000..96b19bd79 --- /dev/null +++ b/packages/design-system/src/utils.ts @@ -0,0 +1,20 @@ +/** + * SUMI Design System — Utility Functions + * + * Canonical implementation of cn() for merging Tailwind classes. + * Also available at apps/web/src/lib/utils.ts. + */ + +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +/** + * Merge Tailwind CSS classes with deduplication. + * Combines clsx (conditional classes) + tailwind-merge (conflict resolution). + * + * @example + * cn('px-4 py-2', isActive && 'bg-primary', className) + */ +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)); +} diff --git a/packages/design-system/tsconfig.json b/packages/design-system/tsconfig.json new file mode 100644 index 000000000..dcfa6a747 --- /dev/null +++ b/packages/design-system/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +}