@umituz/web-design-system 6.11.0 → 6.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,79 @@
1
+ import type { ThemeConfig } from '../../domain/types';
2
+
3
+ export const midnightTheme: ThemeConfig = {
4
+ name: 'midnight',
5
+ mode: 'dark',
6
+ colors: {
7
+ background: '#060a12',
8
+ foreground: '#e8edf8',
9
+ card: '#0d1422',
10
+ cardForeground: '#e8edf8',
11
+ popover: '#121b2c',
12
+ popoverForeground: '#e8edf8',
13
+ primary: '#4e8cff',
14
+ primaryForeground: '#ffffff',
15
+ primarySoft: 'rgba(78, 140, 255, 0.13)',
16
+ secondary: '#121b2c',
17
+ secondaryForeground: '#e8edf8',
18
+ muted: '#121b2c',
19
+ mutedForeground: '#9aa7c0',
20
+ accent: '#182338',
21
+ accentForeground: '#e8edf8',
22
+ border: '#27334a',
23
+ input: '#27334a',
24
+ destructive: '#f4585f',
25
+ destructiveForeground: '#ffffff',
26
+ ring: 'rgba(78, 140, 255, 0.45)',
27
+ success: '#3ddc97',
28
+ successForeground: '#ffffff',
29
+ warning: '#f5b04a',
30
+ warningForeground: '#060a12',
31
+ info: '#4e8cff',
32
+ infoForeground: '#ffffff',
33
+ },
34
+ typography: {
35
+ fontSans: "'IBM Plex Sans', -apple-system, 'Segoe UI', sans-serif",
36
+ fontMono: "'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace",
37
+ },
38
+ radii: {
39
+ xs: '3px',
40
+ sm: '5px',
41
+ md: '8px',
42
+ lg: '12px',
43
+ xl: '16px',
44
+ },
45
+ customVars: {
46
+ 'bg-0': '#060a12',
47
+ 'bg-1': '#090f1a',
48
+ 'bg-2': '#0d1422',
49
+ 'bg-3': '#121b2c',
50
+ 'bg-4': '#182338',
51
+ 'bg-inset': '#070c15',
52
+ 'line-1': '#1b2536',
53
+ 'line-2': '#27334a',
54
+ 'line-3': '#35435f',
55
+ 'ink-1': '#e8edf8',
56
+ 'ink-2': '#9aa7c0',
57
+ 'ink-3': '#5e6c88',
58
+ 'ink-4': '#3d4a63',
59
+ 'blue': '#4e8cff',
60
+ 'blue-soft': 'rgba(78, 140, 255, 0.13)',
61
+ 'blue-line': 'rgba(78, 140, 255, 0.35)',
62
+ 'green': '#3ddc97',
63
+ 'green-soft': 'rgba(61, 220, 151, 0.12)',
64
+ 'green-line': 'rgba(61, 220, 151, 0.35)',
65
+ 'amber': '#f5b04a',
66
+ 'amber-soft': 'rgba(245, 176, 74, 0.13)',
67
+ 'amber-line': 'rgba(245, 176, 74, 0.35)',
68
+ 'red': '#f4585f',
69
+ 'red-soft': 'rgba(244, 88, 95, 0.12)',
70
+ 'red-line': 'rgba(244, 88, 95, 0.4)',
71
+ 'idle': '#7683a0',
72
+ 'idle-soft': 'rgba(118, 131, 160, 0.14)',
73
+ 'shadow-1': '0 1px 2px rgba(2, 6, 14, 0.5)',
74
+ 'shadow-2': '0 8px 28px rgba(2, 6, 14, 0.55)',
75
+ 'shadow-3': '0 18px 60px rgba(1, 4, 10, 0.7)',
76
+ 'sidebar-w': '250px',
77
+ 'topbar-h': '56px',
78
+ },
79
+ };
@@ -0,0 +1,71 @@
1
+ import type { ThemeConfig } from '../../domain/types';
2
+
3
+ export const oceanTheme: ThemeConfig = {
4
+ name: 'ocean',
5
+ mode: 'light',
6
+ colors: {
7
+ background: '#f8fafc',
8
+ foreground: '#0f172a',
9
+ card: '#ffffff',
10
+ cardForeground: '#0f172a',
11
+ popover: '#ffffff',
12
+ popoverForeground: '#0f172a',
13
+ primary: '#0284c7',
14
+ primaryForeground: '#ffffff',
15
+ primarySoft: 'rgba(2, 132, 199, 0.1)',
16
+ secondary: '#0ea5e9',
17
+ secondaryForeground: '#ffffff',
18
+ muted: '#f0f9ff',
19
+ mutedForeground: '#64748b',
20
+ accent: '#e0f2fe',
21
+ accentForeground: '#0369a1',
22
+ border: '#e2e8f0',
23
+ input: '#e2e8f0',
24
+ destructive: '#ef4444',
25
+ destructiveForeground: '#ffffff',
26
+ ring: 'rgba(2, 132, 199, 0.45)',
27
+ success: '#10b981',
28
+ successForeground: '#ffffff',
29
+ warning: '#f59e0b',
30
+ warningForeground: '#ffffff',
31
+ info: '#0284c7',
32
+ infoForeground: '#ffffff',
33
+ },
34
+ darkColors: {
35
+ background: '#08111e',
36
+ foreground: '#f0f9ff',
37
+ card: '#0c1a2e',
38
+ cardForeground: '#f0f9ff',
39
+ popover: '#0c1a2e',
40
+ popoverForeground: '#f0f9ff',
41
+ primary: '#38bdf8',
42
+ primaryForeground: '#08111e',
43
+ secondary: '#0284c7',
44
+ secondaryForeground: '#ffffff',
45
+ muted: '#13233b',
46
+ mutedForeground: '#94a3b8',
47
+ accent: '#183052',
48
+ accentForeground: '#7dd3fc',
49
+ border: '#1b3252',
50
+ input: '#1b3252',
51
+ destructive: '#f87171',
52
+ destructiveForeground: '#ffffff',
53
+ ring: 'rgba(56, 189, 248, 0.5)',
54
+ success: '#34d399',
55
+ successForeground: '#ffffff',
56
+ warning: '#fbbf24',
57
+ warningForeground: '#ffffff',
58
+ info: '#38bdf8',
59
+ infoForeground: '#ffffff',
60
+ },
61
+ typography: {
62
+ fontSans: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
63
+ fontMono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
64
+ },
65
+ radii: {
66
+ sm: '6px',
67
+ md: '8px',
68
+ lg: '12px',
69
+ xl: '16px',
70
+ },
71
+ };
@@ -0,0 +1,28 @@
1
+ import type { ThemeConfig } from '../../domain/types';
2
+ import { defaultTheme } from './default.theme';
3
+ import { oceanTheme } from './ocean.theme';
4
+ import { midnightTheme } from './midnight.theme';
5
+ import { editorialTheme } from './editorial.theme';
6
+
7
+ const themeRegistry = new Map<string, ThemeConfig>([
8
+ ['default', defaultTheme],
9
+ ['ocean', oceanTheme],
10
+ ['midnight', midnightTheme],
11
+ ['editorial', editorialTheme],
12
+ ]);
13
+
14
+ export function registerTheme(theme: ThemeConfig): void {
15
+ themeRegistry.set(theme.name, theme);
16
+ }
17
+
18
+ export function getTheme(name: string): ThemeConfig {
19
+ return themeRegistry.get(name) ?? defaultTheme;
20
+ }
21
+
22
+ export function hasTheme(name: string): boolean {
23
+ return themeRegistry.has(name);
24
+ }
25
+
26
+ export function listThemes(): string[] {
27
+ return Array.from(themeRegistry.keys());
28
+ }
@@ -0,0 +1,129 @@
1
+ import { forwardRef, type HTMLAttributes, type ElementType } from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cn } from '../../infrastructure/utils';
4
+ import type { BaseProps } from '../../domain/types';
5
+
6
+ export type GridColsNumber = 1 | 2 | 3 | 4 | 5 | 6 | 12;
7
+
8
+ export interface ResponsiveGridCols {
9
+ base?: GridColsNumber;
10
+ sm?: GridColsNumber;
11
+ md?: GridColsNumber;
12
+ lg?: GridColsNumber;
13
+ xl?: GridColsNumber;
14
+ }
15
+
16
+ export type GridCols = GridColsNumber | ResponsiveGridCols;
17
+ export type GridGap = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
18
+
19
+ export interface GridProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
20
+ cols?: GridCols;
21
+ gap?: GridGap;
22
+ asChild?: boolean;
23
+ as?: ElementType;
24
+ }
25
+
26
+ const gapStyles: Record<GridGap, string> = {
27
+ none: 'gap-0',
28
+ xs: 'gap-1',
29
+ sm: 'gap-2',
30
+ md: 'gap-4',
31
+ lg: 'gap-6',
32
+ xl: 'gap-8',
33
+ '2xl': 'gap-12',
34
+ };
35
+
36
+ const baseColStyles: Record<GridColsNumber, string> = {
37
+ 1: 'grid-cols-1',
38
+ 2: 'grid-cols-2',
39
+ 3: 'grid-cols-3',
40
+ 4: 'grid-cols-4',
41
+ 5: 'grid-cols-5',
42
+ 6: 'grid-cols-6',
43
+ 12: 'grid-cols-12',
44
+ };
45
+
46
+ const smColStyles: Record<GridColsNumber, string> = {
47
+ 1: 'sm:grid-cols-1',
48
+ 2: 'sm:grid-cols-2',
49
+ 3: 'sm:grid-cols-3',
50
+ 4: 'sm:grid-cols-4',
51
+ 5: 'sm:grid-cols-5',
52
+ 6: 'sm:grid-cols-6',
53
+ 12: 'sm:grid-cols-12',
54
+ };
55
+
56
+ const mdColStyles: Record<GridColsNumber, string> = {
57
+ 1: 'md:grid-cols-1',
58
+ 2: 'md:grid-cols-2',
59
+ 3: 'md:grid-cols-3',
60
+ 4: 'md:grid-cols-4',
61
+ 5: 'md:grid-cols-5',
62
+ 6: 'md:grid-cols-6',
63
+ 12: 'md:grid-cols-12',
64
+ };
65
+
66
+ const lgColStyles: Record<GridColsNumber, string> = {
67
+ 1: 'lg:grid-cols-1',
68
+ 2: 'lg:grid-cols-2',
69
+ 3: 'lg:grid-cols-3',
70
+ 4: 'lg:grid-cols-4',
71
+ 5: 'lg:grid-cols-5',
72
+ 6: 'lg:grid-cols-6',
73
+ 12: 'lg:grid-cols-12',
74
+ };
75
+
76
+ const xlColStyles: Record<GridColsNumber, string> = {
77
+ 1: 'xl:grid-cols-1',
78
+ 2: 'xl:grid-cols-2',
79
+ 3: 'xl:grid-cols-3',
80
+ 4: 'xl:grid-cols-4',
81
+ 5: 'xl:grid-cols-5',
82
+ 6: 'xl:grid-cols-6',
83
+ 12: 'xl:grid-cols-12',
84
+ };
85
+
86
+ function resolveColClasses(cols: GridCols): string {
87
+ if (typeof cols === 'number') {
88
+ return baseColStyles[cols] ?? 'grid-cols-1';
89
+ }
90
+
91
+ const classes: string[] = [];
92
+ if (cols.base) classes.push(baseColStyles[cols.base]);
93
+ if (cols.sm) classes.push(smColStyles[cols.sm]);
94
+ if (cols.md) classes.push(mdColStyles[cols.md]);
95
+ if (cols.lg) classes.push(lgColStyles[cols.lg]);
96
+ if (cols.xl) classes.push(xlColStyles[cols.xl]);
97
+
98
+ return classes.length > 0 ? classes.join(' ') : 'grid-cols-1';
99
+ }
100
+
101
+ export const Grid = forwardRef<HTMLDivElement, GridProps>(
102
+ (
103
+ {
104
+ className,
105
+ cols = 1,
106
+ gap = 'md',
107
+ asChild = false,
108
+ as: Component = 'div',
109
+ ...props
110
+ },
111
+ ref
112
+ ) => {
113
+ const Comp = asChild ? Slot : Component;
114
+ return (
115
+ <Comp
116
+ ref={ref}
117
+ className={cn(
118
+ 'grid w-full min-w-0',
119
+ resolveColClasses(cols),
120
+ gapStyles[gap],
121
+ className
122
+ )}
123
+ {...props}
124
+ />
125
+ );
126
+ }
127
+ );
128
+
129
+ Grid.displayName = 'Grid';
@@ -0,0 +1,81 @@
1
+ import { forwardRef, type HTMLAttributes, type ElementType } from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cn } from '../../infrastructure/utils';
4
+ import type { BaseProps } from '../../domain/types';
5
+
6
+ export type RowGap = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
7
+ export type RowAlign = 'start' | 'center' | 'end' | 'stretch' | 'baseline';
8
+ export type RowJustify = 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
9
+
10
+ export interface RowProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
11
+ gap?: RowGap;
12
+ align?: RowAlign;
13
+ justify?: RowJustify;
14
+ wrap?: boolean;
15
+ inline?: boolean;
16
+ asChild?: boolean;
17
+ as?: ElementType;
18
+ }
19
+
20
+ const gapStyles: Record<RowGap, string> = {
21
+ none: 'gap-0',
22
+ xs: 'gap-1',
23
+ sm: 'gap-2',
24
+ md: 'gap-4',
25
+ lg: 'gap-6',
26
+ xl: 'gap-8',
27
+ '2xl': 'gap-12',
28
+ };
29
+
30
+ const alignStyles: Record<RowAlign, string> = {
31
+ start: 'items-start',
32
+ center: 'items-center',
33
+ end: 'items-end',
34
+ stretch: 'items-stretch',
35
+ baseline: 'items-baseline',
36
+ };
37
+
38
+ const justifyStyles: Record<RowJustify, string> = {
39
+ start: 'justify-start',
40
+ center: 'justify-center',
41
+ end: 'justify-end',
42
+ between: 'justify-between',
43
+ around: 'justify-around',
44
+ evenly: 'justify-evenly',
45
+ };
46
+
47
+ export const Row = forwardRef<HTMLDivElement, RowProps>(
48
+ (
49
+ {
50
+ className,
51
+ gap = 'sm',
52
+ align = 'center',
53
+ justify = 'start',
54
+ wrap = false,
55
+ inline = false,
56
+ asChild = false,
57
+ as: Component = 'div',
58
+ ...props
59
+ },
60
+ ref
61
+ ) => {
62
+ const Comp = asChild ? Slot : Component;
63
+ return (
64
+ <Comp
65
+ ref={ref}
66
+ className={cn(
67
+ inline ? 'inline-flex' : 'flex',
68
+ 'flex-row min-w-0',
69
+ gapStyles[gap],
70
+ alignStyles[align],
71
+ justifyStyles[justify],
72
+ wrap ? 'flex-wrap' : 'flex-nowrap',
73
+ className
74
+ )}
75
+ {...props}
76
+ />
77
+ );
78
+ }
79
+ );
80
+
81
+ Row.displayName = 'Row';
@@ -0,0 +1,78 @@
1
+ import { forwardRef, type HTMLAttributes, type ElementType } from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cn } from '../../infrastructure/utils';
4
+ import type { BaseProps } from '../../domain/types';
5
+
6
+ export type StackGap = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
7
+ export type StackAlign = 'start' | 'center' | 'end' | 'stretch' | 'baseline';
8
+ export type StackJustify = 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
9
+
10
+ export interface StackProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
11
+ gap?: StackGap;
12
+ align?: StackAlign;
13
+ justify?: StackJustify;
14
+ wrap?: boolean;
15
+ asChild?: boolean;
16
+ as?: ElementType;
17
+ }
18
+
19
+ const gapStyles: Record<StackGap, string> = {
20
+ none: 'gap-0',
21
+ xs: 'gap-1',
22
+ sm: 'gap-2',
23
+ md: 'gap-4',
24
+ lg: 'gap-6',
25
+ xl: 'gap-8',
26
+ '2xl': 'gap-12',
27
+ };
28
+
29
+ const alignStyles: Record<StackAlign, string> = {
30
+ start: 'items-start',
31
+ center: 'items-center',
32
+ end: 'items-end',
33
+ stretch: 'items-stretch',
34
+ baseline: 'items-baseline',
35
+ };
36
+
37
+ const justifyStyles: Record<StackJustify, string> = {
38
+ start: 'justify-start',
39
+ center: 'justify-center',
40
+ end: 'justify-end',
41
+ between: 'justify-between',
42
+ around: 'justify-around',
43
+ evenly: 'justify-evenly',
44
+ };
45
+
46
+ export const Stack = forwardRef<HTMLDivElement, StackProps>(
47
+ (
48
+ {
49
+ className,
50
+ gap = 'md',
51
+ align = 'stretch',
52
+ justify = 'start',
53
+ wrap = false,
54
+ asChild = false,
55
+ as: Component = 'div',
56
+ ...props
57
+ },
58
+ ref
59
+ ) => {
60
+ const Comp = asChild ? Slot : Component;
61
+ return (
62
+ <Comp
63
+ ref={ref}
64
+ className={cn(
65
+ 'flex flex-col min-w-0',
66
+ gapStyles[gap],
67
+ alignStyles[align],
68
+ justifyStyles[justify],
69
+ wrap && 'flex-wrap',
70
+ className
71
+ )}
72
+ {...props}
73
+ />
74
+ );
75
+ }
76
+ );
77
+
78
+ Stack.displayName = 'Stack';
@@ -85,9 +85,18 @@ export type { KbdProps } from './Kbd';
85
85
  export { Money } from './Money';
86
86
  export type { MoneyProps } from './Money';
87
87
 
88
- // New Productivity Atoms
89
88
  export { CopyButton } from './CopyButton';
90
89
  export type { CopyButtonProps } from './CopyButton';
91
90
 
92
91
  export { StatusDot } from './StatusDot';
93
92
  export type { StatusDotProps, StatusType } from './StatusDot';
93
+
94
+ export { Stack } from './Stack';
95
+ export type { StackProps, StackGap, StackAlign, StackJustify } from './Stack';
96
+
97
+ export { Row } from './Row';
98
+ export type { RowProps, RowGap, RowAlign, RowJustify } from './Row';
99
+
100
+ export { Grid } from './Grid';
101
+ export type { GridProps, GridCols, GridColsNumber, ResponsiveGridCols, GridGap } from './Grid';
102
+
@@ -0,0 +1,111 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useMemo,
6
+ useState,
7
+ type ReactNode,
8
+ } from 'react';
9
+ import type { ThemeConfig, ThemeMode } from '../../domain/types';
10
+ import { applyTheme, getTheme } from '../../infrastructure/themes';
11
+
12
+ export interface DesignSystemContextValue {
13
+ theme: ThemeConfig;
14
+ mode: 'light' | 'dark';
15
+ setTheme: (theme: string | ThemeConfig) => void;
16
+ setMode: (mode: 'light' | 'dark') => void;
17
+ toggleMode: () => void;
18
+ }
19
+
20
+ const DesignSystemContext = createContext<DesignSystemContextValue | null>(null);
21
+
22
+ export interface DesignSystemProviderProps {
23
+ children: ReactNode;
24
+ theme?: string | ThemeConfig;
25
+ mode?: ThemeMode;
26
+ storageKey?: string;
27
+ }
28
+
29
+ export function DesignSystemProvider({
30
+ children,
31
+ theme: initialTheme = 'default',
32
+ mode: initialMode = 'system',
33
+ storageKey = 'umituz-theme-mode',
34
+ }: DesignSystemProviderProps) {
35
+ const [overrideTheme, setOverrideTheme] = useState<ThemeConfig | null>(null);
36
+
37
+ const currentTheme = useMemo(() => {
38
+ if (overrideTheme) return overrideTheme;
39
+ return typeof initialTheme === 'string' ? getTheme(initialTheme) : initialTheme;
40
+ }, [initialTheme, overrideTheme]);
41
+
42
+ const [activeMode, setActiveMode] = useState<'light' | 'dark'>(() => {
43
+ if (typeof window === 'undefined') {
44
+ return currentTheme.mode ?? 'light';
45
+ }
46
+
47
+ if (storageKey) {
48
+ const stored = localStorage.getItem(storageKey);
49
+ if (stored === 'light' || stored === 'dark') return stored;
50
+ }
51
+
52
+ if (initialMode === 'dark' || initialMode === 'light') {
53
+ return initialMode;
54
+ }
55
+
56
+ if (currentTheme.mode) {
57
+ return currentTheme.mode;
58
+ }
59
+
60
+ return window.matchMedia('(prefers-color-scheme: dark)').matches
61
+ ? 'dark'
62
+ : 'light';
63
+ });
64
+
65
+ useEffect(() => {
66
+ applyTheme(currentTheme);
67
+
68
+ const doc = document.documentElement;
69
+ if (activeMode === 'dark') {
70
+ doc.classList.add('dark');
71
+ doc.setAttribute('data-theme', 'dark');
72
+ } else {
73
+ doc.classList.remove('dark');
74
+ doc.setAttribute('data-theme', 'light');
75
+ }
76
+
77
+ if (storageKey) {
78
+ localStorage.setItem(storageKey, activeMode);
79
+ }
80
+ }, [currentTheme, activeMode, storageKey]);
81
+
82
+ const value = useMemo<DesignSystemContextValue>(() => {
83
+ return {
84
+ theme: currentTheme,
85
+ mode: activeMode,
86
+ setTheme: (t: string | ThemeConfig) => {
87
+ setOverrideTheme(typeof t === 'string' ? getTheme(t) : t);
88
+ },
89
+ setMode: (m: 'light' | 'dark') => {
90
+ setActiveMode(m);
91
+ },
92
+ toggleMode: () => {
93
+ setActiveMode((prev) => (prev === 'dark' ? 'light' : 'dark'));
94
+ },
95
+ };
96
+ }, [currentTheme, activeMode]);
97
+
98
+ return (
99
+ <DesignSystemContext.Provider value={value}>
100
+ {children}
101
+ </DesignSystemContext.Provider>
102
+ );
103
+ }
104
+
105
+ export function useDesignSystem(): DesignSystemContextValue {
106
+ const ctx = useContext(DesignSystemContext);
107
+ if (!ctx) {
108
+ throw new Error('useDesignSystem must be used within a DesignSystemProvider');
109
+ }
110
+ return ctx;
111
+ }
@@ -0,0 +1,5 @@
1
+ export { DesignSystemProvider, useDesignSystem } from './DesignSystemProvider';
2
+ export type {
3
+ DesignSystemProviderProps,
4
+ DesignSystemContextValue,
5
+ } from './DesignSystemProvider';