@fabio.caffarello/react-design-system 1.6.0 → 1.7.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.
- package/dist/index.cjs +29 -4
- package/dist/index.js +1866 -716
- package/dist/ui/atoms/Button/Button.d.ts +28 -5
- package/dist/ui/atoms/Button/Button.stories.d.ts +11 -3
- package/dist/ui/atoms/Checkbox/Checkbox.d.ts +24 -0
- package/dist/ui/atoms/Checkbox/Checkbox.stories.d.ts +10 -0
- package/dist/ui/atoms/Checkbox/Checkbox.test.d.ts +1 -0
- package/dist/ui/atoms/Collapsible/Collapsible.d.ts +29 -0
- package/dist/ui/atoms/Collapsible/Collapsible.stories.d.ts +9 -0
- package/dist/ui/atoms/Collapsible/Collapsible.test.d.ts +1 -0
- package/dist/ui/atoms/Input/Input.d.ts +28 -4
- package/dist/ui/atoms/Input/Input.stories.d.ts +8 -3
- package/dist/ui/atoms/Radio/Radio.d.ts +26 -0
- package/dist/ui/atoms/Radio/Radio.stories.d.ts +10 -0
- package/dist/ui/atoms/Radio/Radio.test.d.ts +1 -0
- package/dist/ui/atoms/SidebarItem/SidebarItem.d.ts +3 -1
- package/dist/ui/atoms/SidebarItem/SidebarItem.stories.d.ts +3 -0
- package/dist/ui/atoms/index.d.ts +7 -0
- package/dist/ui/hooks/useCollapsible.d.ts +27 -0
- package/dist/ui/index.d.ts +13 -0
- package/dist/ui/molecules/InputWithLabel/InputWithLabel.d.ts +4 -2
- package/dist/ui/molecules/SidebarGroup/SidebarGroup.d.ts +8 -1
- package/dist/ui/molecules/SidebarGroup/SidebarGroup.stories.d.ts +11 -0
- package/dist/ui/molecules/SidebarGroup/SidebarGroup.test.d.ts +1 -0
- package/dist/ui/providers/ThemeProvider.d.ts +34 -0
- package/dist/ui/tokens/breakpoints.d.ts +36 -0
- package/dist/ui/tokens/colors.d.ts +89 -0
- package/dist/ui/tokens/sidebar.d.ts +48 -0
- package/dist/ui/tokens/spacing.d.ts +53 -0
- package/dist/ui/tokens/themes/dark.d.ts +38 -0
- package/dist/ui/tokens/themes/light.d.ts +38 -0
- package/dist/ui/tokens/tokens.factory.d.ts +57 -0
- package/dist/ui/tokens/typography.d.ts +90 -0
- package/package.json +3 -2
- package/src/ui/atoms/Button/Button.stories.tsx +77 -7
- package/src/ui/atoms/Button/Button.tsx +176 -28
- package/src/ui/atoms/Checkbox/Checkbox.stories.tsx +61 -0
- package/src/ui/atoms/Checkbox/Checkbox.test.tsx +32 -0
- package/src/ui/atoms/Checkbox/Checkbox.tsx +103 -0
- package/src/ui/atoms/Collapsible/Collapsible.tsx +2 -2
- package/src/ui/atoms/Input/Input.stories.tsx +67 -6
- package/src/ui/atoms/Input/Input.tsx +117 -14
- package/src/ui/atoms/Radio/Radio.stories.tsx +72 -0
- package/src/ui/atoms/Radio/Radio.test.tsx +32 -0
- package/src/ui/atoms/Radio/Radio.tsx +104 -0
- package/src/ui/atoms/index.ts +7 -0
- package/src/ui/index.ts +14 -0
- package/src/ui/molecules/InputWithLabel/InputWithLabel.tsx +5 -4
- package/src/ui/molecules/SidebarGroup/SidebarGroup.tsx +30 -38
- package/src/ui/providers/ThemeProvider.tsx +105 -0
- package/src/ui/tokens/breakpoints.ts +71 -0
- package/src/ui/tokens/colors.ts +250 -0
- package/src/ui/tokens/sidebar.ts +9 -3
- package/src/ui/tokens/spacing.ts +127 -0
- package/src/ui/tokens/themes/dark.ts +18 -0
- package/src/ui/tokens/themes/light.ts +18 -0
- package/src/ui/tokens/tokens.factory.ts +117 -0
- package/src/ui/tokens/typography.ts +191 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type { HTMLAttributes, ReactNode } from "react";
|
|
4
|
+
import { ChevronRight } from "lucide-react";
|
|
4
5
|
import { Text } from "../../atoms";
|
|
5
6
|
import Collapsible from "../../atoms/Collapsible/Collapsible";
|
|
6
7
|
import { SIDEBAR_TOKENS } from "../../tokens/sidebar";
|
|
7
8
|
|
|
8
9
|
export interface SidebarGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
9
10
|
title?: string;
|
|
11
|
+
titleIcon?: ReactNode; // Optional icon for the title
|
|
10
12
|
children: ReactNode;
|
|
11
13
|
collapsible?: boolean;
|
|
12
14
|
defaultCollapsed?: boolean;
|
|
@@ -32,6 +34,7 @@ export interface SidebarGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
32
34
|
*/
|
|
33
35
|
export default function SidebarGroup({
|
|
34
36
|
title,
|
|
37
|
+
titleIcon,
|
|
35
38
|
children,
|
|
36
39
|
collapsible = false,
|
|
37
40
|
defaultCollapsed = false,
|
|
@@ -45,38 +48,13 @@ export default function SidebarGroup({
|
|
|
45
48
|
const baseClasses = ["space-y-1"];
|
|
46
49
|
const classes = [...baseClasses, className].filter(Boolean).join(" ");
|
|
47
50
|
|
|
48
|
-
// Chevron icon component
|
|
51
|
+
// Chevron icon component using lucide-react
|
|
49
52
|
const ChevronIcon = ({ isOpen }: { isOpen: boolean }) => (
|
|
50
|
-
<
|
|
51
|
-
className={
|
|
53
|
+
<ChevronRight
|
|
54
|
+
className={`${SIDEBAR_TOKENS.chevron.size} ${SIDEBAR_TOKENS.chevron.color} transition-transform duration-200 ${
|
|
52
55
|
isOpen ? 'rotate-90' : ''
|
|
53
56
|
}`}
|
|
54
|
-
|
|
55
|
-
viewBox="0 0 24 24"
|
|
56
|
-
stroke="currentColor"
|
|
57
|
-
>
|
|
58
|
-
<path
|
|
59
|
-
strokeLinecap="round"
|
|
60
|
-
strokeLinejoin="round"
|
|
61
|
-
strokeWidth={2}
|
|
62
|
-
d="M9 5l7 7-7 7"
|
|
63
|
-
/>
|
|
64
|
-
</svg>
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
// Title content with optional chevron
|
|
68
|
-
const titleContent = (
|
|
69
|
-
<>
|
|
70
|
-
<Text
|
|
71
|
-
as="h3"
|
|
72
|
-
className={`${SIDEBAR_TOKENS.text.xs} font-semibold ${SIDEBAR_TOKENS.colors.groupTitle} uppercase tracking-wider`}
|
|
73
|
-
>
|
|
74
|
-
{title}
|
|
75
|
-
</Text>
|
|
76
|
-
{collapsible && showChevron && (
|
|
77
|
-
<ChevronIcon isOpen={collapsed !== undefined ? !collapsed : !defaultCollapsed} />
|
|
78
|
-
)}
|
|
79
|
-
</>
|
|
57
|
+
/>
|
|
80
58
|
);
|
|
81
59
|
|
|
82
60
|
// If collapsible and has title, use Collapsible component
|
|
@@ -88,15 +66,24 @@ export default function SidebarGroup({
|
|
|
88
66
|
onOpenChange={(open) => onCollapseChange?.(!open)}
|
|
89
67
|
storageKey={storageKey}
|
|
90
68
|
trigger={
|
|
91
|
-
<div className={`${SIDEBAR_TOKENS.spacing.groupTitlePadding} flex items-center justify-between w-full`}>
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
69
|
+
<div className={`${SIDEBAR_TOKENS.spacing.groupTitlePadding} flex items-center justify-between w-full hover:bg-gray-50 rounded-md transition-colors cursor-pointer`}>
|
|
70
|
+
<div className="flex items-center gap-2">
|
|
71
|
+
{titleIcon && (
|
|
72
|
+
<span className={`${SIDEBAR_TOKENS.icon.md} ${SIDEBAR_TOKENS.colors.groupTitle}`}>
|
|
73
|
+
{titleIcon}
|
|
74
|
+
</span>
|
|
75
|
+
)}
|
|
76
|
+
<Text
|
|
77
|
+
as="h3"
|
|
78
|
+
className={`${SIDEBAR_TOKENS.text.xs} font-semibold ${SIDEBAR_TOKENS.colors.groupTitle} uppercase tracking-wider`}
|
|
79
|
+
>
|
|
80
|
+
{title}
|
|
81
|
+
</Text>
|
|
82
|
+
</div>
|
|
98
83
|
{showChevron && (
|
|
99
|
-
<
|
|
84
|
+
<span className="ml-2">
|
|
85
|
+
<ChevronIcon isOpen={collapsed !== undefined ? !collapsed : !defaultCollapsed} />
|
|
86
|
+
</span>
|
|
100
87
|
)}
|
|
101
88
|
</div>
|
|
102
89
|
}
|
|
@@ -112,7 +99,12 @@ export default function SidebarGroup({
|
|
|
112
99
|
return (
|
|
113
100
|
<div className={classes} {...props}>
|
|
114
101
|
{title && (
|
|
115
|
-
<div className={SIDEBAR_TOKENS.spacing.groupTitlePadding}>
|
|
102
|
+
<div className={`${SIDEBAR_TOKENS.spacing.groupTitlePadding} flex items-center gap-2`}>
|
|
103
|
+
{titleIcon && (
|
|
104
|
+
<span className={`${SIDEBAR_TOKENS.icon.md} ${SIDEBAR_TOKENS.colors.groupTitle}`}>
|
|
105
|
+
{titleIcon}
|
|
106
|
+
</span>
|
|
107
|
+
)}
|
|
116
108
|
<Text
|
|
117
109
|
as="h3"
|
|
118
110
|
className={`${SIDEBAR_TOKENS.text.xs} font-semibold ${SIDEBAR_TOKENS.colors.groupTitle} uppercase tracking-wider`}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
|
|
4
|
+
import { COLOR_TOKENS_LIGHT, COLOR_TOKENS_DARK, type ColorRole, type SemanticColor } from '../tokens/colors';
|
|
5
|
+
import type { ThemeMode } from '../tokens/tokens.factory';
|
|
6
|
+
|
|
7
|
+
export interface ThemeContextValue {
|
|
8
|
+
theme: ThemeMode;
|
|
9
|
+
toggleTheme: () => void;
|
|
10
|
+
setTheme: (theme: ThemeMode) => void;
|
|
11
|
+
colors: Record<ColorRole, SemanticColor>;
|
|
12
|
+
isDark: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
16
|
+
|
|
17
|
+
export interface ThemeProviderProps {
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
defaultTheme?: ThemeMode;
|
|
20
|
+
storageKey?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ThemeProvider Component
|
|
25
|
+
*
|
|
26
|
+
* Provides theme context to the application.
|
|
27
|
+
* Uses Strategy Pattern for different theme strategies (light, dark).
|
|
28
|
+
* Uses Observer Pattern to notify components about theme changes.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* <ThemeProvider defaultTheme="light">
|
|
33
|
+
* <App />
|
|
34
|
+
* </ThemeProvider>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function ThemeProvider({
|
|
38
|
+
children,
|
|
39
|
+
defaultTheme = 'light',
|
|
40
|
+
storageKey = 'theme',
|
|
41
|
+
}: ThemeProviderProps) {
|
|
42
|
+
const [theme, setThemeState] = useState<ThemeMode>(() => {
|
|
43
|
+
if (typeof window === 'undefined') {
|
|
44
|
+
return defaultTheme;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const stored = localStorage.getItem(storageKey);
|
|
49
|
+
if (stored === 'light' || stored === 'dark') {
|
|
50
|
+
return stored;
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.warn('Failed to read theme from localStorage:', error);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return defaultTheme;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
try {
|
|
61
|
+
localStorage.setItem(storageKey, theme);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn('Failed to save theme to localStorage:', error);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Apply theme class to document root
|
|
67
|
+
document.documentElement.classList.remove('light', 'dark');
|
|
68
|
+
document.documentElement.classList.add(theme);
|
|
69
|
+
}, [theme, storageKey]);
|
|
70
|
+
|
|
71
|
+
const toggleTheme = () => {
|
|
72
|
+
setThemeState((prev) => (prev === 'light' ? 'dark' : 'light'));
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const setTheme = (newTheme: ThemeMode) => {
|
|
76
|
+
setThemeState(newTheme);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const colors = theme === 'light' ? COLOR_TOKENS_LIGHT : COLOR_TOKENS_DARK;
|
|
80
|
+
|
|
81
|
+
const value: ThemeContextValue = {
|
|
82
|
+
theme,
|
|
83
|
+
toggleTheme,
|
|
84
|
+
setTheme,
|
|
85
|
+
colors,
|
|
86
|
+
isDark: theme === 'dark',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<ThemeContext.Provider value={value}>
|
|
91
|
+
{children}
|
|
92
|
+
</ThemeContext.Provider>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Hook to use theme context
|
|
98
|
+
*/
|
|
99
|
+
export function useTheme(): ThemeContextValue {
|
|
100
|
+
const context = useContext(ThemeContext);
|
|
101
|
+
if (context === undefined) {
|
|
102
|
+
throw new Error('useTheme must be used within a ThemeProvider');
|
|
103
|
+
}
|
|
104
|
+
return context;
|
|
105
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Breakpoint Tokens
|
|
3
|
+
*
|
|
4
|
+
* Responsive breakpoints for consistent media queries.
|
|
5
|
+
* Uses Factory Pattern for type-safe breakpoint creation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type BreakpointName = 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
9
|
+
|
|
10
|
+
export interface BreakpointToken {
|
|
11
|
+
name: BreakpointName;
|
|
12
|
+
minWidth: number;
|
|
13
|
+
px: string;
|
|
14
|
+
rem: string;
|
|
15
|
+
tailwind: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Breakpoint Token Factory
|
|
20
|
+
* Creates breakpoint tokens with consistent values
|
|
21
|
+
*/
|
|
22
|
+
export class BreakpointTokenFactory {
|
|
23
|
+
/**
|
|
24
|
+
* Create breakpoint token
|
|
25
|
+
*/
|
|
26
|
+
static create(name: BreakpointName): BreakpointToken {
|
|
27
|
+
const breakpointMap: Record<BreakpointName, { minWidth: number; tailwind: string }> = {
|
|
28
|
+
sm: { minWidth: 640, tailwind: 'sm' },
|
|
29
|
+
md: { minWidth: 768, tailwind: 'md' },
|
|
30
|
+
lg: { minWidth: 1024, tailwind: 'lg' },
|
|
31
|
+
xl: { minWidth: 1280, tailwind: 'xl' },
|
|
32
|
+
'2xl': { minWidth: 1536, tailwind: '2xl' },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const config = breakpointMap[name];
|
|
36
|
+
return {
|
|
37
|
+
name,
|
|
38
|
+
minWidth: config.minWidth,
|
|
39
|
+
px: `${config.minWidth}px`,
|
|
40
|
+
rem: `${config.minWidth / 16}rem`,
|
|
41
|
+
tailwind: config.tailwind,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pre-defined breakpoint tokens
|
|
48
|
+
*/
|
|
49
|
+
export const BREAKPOINT_TOKENS: Record<BreakpointName, BreakpointToken> = {
|
|
50
|
+
sm: BreakpointTokenFactory.create('sm'),
|
|
51
|
+
md: BreakpointTokenFactory.create('md'),
|
|
52
|
+
lg: BreakpointTokenFactory.create('lg'),
|
|
53
|
+
xl: BreakpointTokenFactory.create('xl'),
|
|
54
|
+
'2xl': BreakpointTokenFactory.create('2xl'),
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Helper function to get breakpoint token
|
|
59
|
+
*/
|
|
60
|
+
export function getBreakpoint(name: BreakpointName): BreakpointToken {
|
|
61
|
+
return BREAKPOINT_TOKENS[name];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Helper function to create media query string
|
|
66
|
+
*/
|
|
67
|
+
export function getMediaQuery(name: BreakpointName, direction: 'min' | 'max' = 'min'): string {
|
|
68
|
+
const breakpoint = BREAKPOINT_TOKENS[name];
|
|
69
|
+
const operator = direction === 'min' ? 'min-width' : 'max-width';
|
|
70
|
+
return `@media (${operator}: ${breakpoint.px})`;
|
|
71
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color Tokens
|
|
3
|
+
*
|
|
4
|
+
* Semantic color system for consistent theming.
|
|
5
|
+
* Uses Strategy Pattern for different color strategies (light, dark, custom).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type ColorRole =
|
|
9
|
+
| 'primary'
|
|
10
|
+
| 'secondary'
|
|
11
|
+
| 'success'
|
|
12
|
+
| 'warning'
|
|
13
|
+
| 'error'
|
|
14
|
+
| 'info'
|
|
15
|
+
| 'neutral';
|
|
16
|
+
|
|
17
|
+
export type ColorShade = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
|
|
18
|
+
|
|
19
|
+
export interface ColorToken {
|
|
20
|
+
hex: string;
|
|
21
|
+
rgb: string;
|
|
22
|
+
tailwind: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SemanticColor {
|
|
26
|
+
light: ColorToken;
|
|
27
|
+
DEFAULT: ColorToken;
|
|
28
|
+
dark: ColorToken;
|
|
29
|
+
contrast: ColorToken;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ColorPalette {
|
|
33
|
+
[key: number]: ColorToken;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Color Strategy Interface
|
|
38
|
+
* Strategy Pattern for different color generation strategies
|
|
39
|
+
*/
|
|
40
|
+
export interface ColorStrategy {
|
|
41
|
+
generatePrimary(): SemanticColor;
|
|
42
|
+
generateSecondary(): SemanticColor;
|
|
43
|
+
generateSuccess(): SemanticColor;
|
|
44
|
+
generateWarning(): SemanticColor;
|
|
45
|
+
generateError(): SemanticColor;
|
|
46
|
+
generateInfo(): SemanticColor;
|
|
47
|
+
generateNeutral(): SemanticColor;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Light Theme Color Strategy
|
|
52
|
+
*/
|
|
53
|
+
export class LightColorStrategy implements ColorStrategy {
|
|
54
|
+
generatePrimary(): SemanticColor {
|
|
55
|
+
return {
|
|
56
|
+
light: { hex: '#818cf8', rgb: '129, 140, 248', tailwind: 'indigo-400' },
|
|
57
|
+
DEFAULT: { hex: '#6366f1', rgb: '99, 102, 241', tailwind: 'indigo-500' },
|
|
58
|
+
dark: { hex: '#4f46e5', rgb: '79, 70, 229', tailwind: 'indigo-600' },
|
|
59
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
generateSecondary(): SemanticColor {
|
|
64
|
+
return {
|
|
65
|
+
light: { hex: '#a78bfa', rgb: '167, 139, 250', tailwind: 'violet-400' },
|
|
66
|
+
DEFAULT: { hex: '#8b5cf6', rgb: '139, 92, 246', tailwind: 'violet-500' },
|
|
67
|
+
dark: { hex: '#7c3aed', rgb: '124, 58, 237', tailwind: 'violet-600' },
|
|
68
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
generateSuccess(): SemanticColor {
|
|
73
|
+
return {
|
|
74
|
+
light: { hex: '#86efac', rgb: '134, 239, 172', tailwind: 'green-300' },
|
|
75
|
+
DEFAULT: { hex: '#22c55e', rgb: '34, 197, 94', tailwind: 'green-500' },
|
|
76
|
+
dark: { hex: '#16a34a', rgb: '22, 163, 74', tailwind: 'green-600' },
|
|
77
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
generateWarning(): SemanticColor {
|
|
82
|
+
return {
|
|
83
|
+
light: { hex: '#fde047', rgb: '253, 224, 71', tailwind: 'yellow-300' },
|
|
84
|
+
DEFAULT: { hex: '#eab308', rgb: '234, 179, 8', tailwind: 'yellow-500' },
|
|
85
|
+
dark: { hex: '#ca8a04', rgb: '202, 138, 4', tailwind: 'yellow-600' },
|
|
86
|
+
contrast: { hex: '#000000', rgb: '0, 0, 0', tailwind: 'black' },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
generateError(): SemanticColor {
|
|
91
|
+
return {
|
|
92
|
+
light: { hex: '#fca5a5', rgb: '252, 165, 165', tailwind: 'red-300' },
|
|
93
|
+
DEFAULT: { hex: '#ef4444', rgb: '239, 68, 68', tailwind: 'red-500' },
|
|
94
|
+
dark: { hex: '#dc2626', rgb: '220, 38, 38', tailwind: 'red-600' },
|
|
95
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
generateInfo(): SemanticColor {
|
|
100
|
+
return {
|
|
101
|
+
light: { hex: '#93c5fd', rgb: '147, 197, 253', tailwind: 'blue-300' },
|
|
102
|
+
DEFAULT: { hex: '#3b82f6', rgb: '59, 130, 246', tailwind: 'blue-500' },
|
|
103
|
+
dark: { hex: '#2563eb', rgb: '37, 99, 235', tailwind: 'blue-600' },
|
|
104
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
generateNeutral(): SemanticColor {
|
|
109
|
+
return {
|
|
110
|
+
light: { hex: '#f3f4f6', rgb: '243, 244, 246', tailwind: 'gray-100' },
|
|
111
|
+
DEFAULT: { hex: '#6b7280', rgb: '107, 114, 128', tailwind: 'gray-500' },
|
|
112
|
+
dark: { hex: '#374151', rgb: '55, 65, 81', tailwind: 'gray-700' },
|
|
113
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Dark Theme Color Strategy
|
|
120
|
+
*/
|
|
121
|
+
export class DarkColorStrategy implements ColorStrategy {
|
|
122
|
+
generatePrimary(): SemanticColor {
|
|
123
|
+
return {
|
|
124
|
+
light: { hex: '#6366f1', rgb: '99, 102, 241', tailwind: 'indigo-500' },
|
|
125
|
+
DEFAULT: { hex: '#818cf8', rgb: '129, 140, 248', tailwind: 'indigo-400' },
|
|
126
|
+
dark: { hex: '#a5b4fc', rgb: '165, 180, 252', tailwind: 'indigo-300' },
|
|
127
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
generateSecondary(): SemanticColor {
|
|
132
|
+
return {
|
|
133
|
+
light: { hex: '#8b5cf6', rgb: '139, 92, 246', tailwind: 'violet-500' },
|
|
134
|
+
DEFAULT: { hex: '#a78bfa', rgb: '167, 139, 250', tailwind: 'violet-400' },
|
|
135
|
+
dark: { hex: '#c4b5fd', rgb: '196, 181, 253', tailwind: 'violet-300' },
|
|
136
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
generateSuccess(): SemanticColor {
|
|
141
|
+
return {
|
|
142
|
+
light: { hex: '#22c55e', rgb: '34, 197, 94', tailwind: 'green-500' },
|
|
143
|
+
DEFAULT: { hex: '#4ade80', rgb: '74, 222, 128', tailwind: 'green-400' },
|
|
144
|
+
dark: { hex: '#86efac', rgb: '134, 239, 172', tailwind: 'green-300' },
|
|
145
|
+
contrast: { hex: '#000000', rgb: '0, 0, 0', tailwind: 'black' },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
generateWarning(): SemanticColor {
|
|
150
|
+
return {
|
|
151
|
+
light: { hex: '#eab308', rgb: '234, 179, 8', tailwind: 'yellow-500' },
|
|
152
|
+
DEFAULT: { hex: '#facc15', rgb: '250, 204, 21', tailwind: 'yellow-400' },
|
|
153
|
+
dark: { hex: '#fde047', rgb: '253, 224, 71', tailwind: 'yellow-300' },
|
|
154
|
+
contrast: { hex: '#000000', rgb: '0, 0, 0', tailwind: 'black' },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
generateError(): SemanticColor {
|
|
159
|
+
return {
|
|
160
|
+
light: { hex: '#ef4444', rgb: '239, 68, 68', tailwind: 'red-500' },
|
|
161
|
+
DEFAULT: { hex: '#f87171', rgb: '248, 113, 113', tailwind: 'red-400' },
|
|
162
|
+
dark: { hex: '#fca5a5', rgb: '252, 165, 165', tailwind: 'red-300' },
|
|
163
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
generateInfo(): SemanticColor {
|
|
168
|
+
return {
|
|
169
|
+
light: { hex: '#3b82f6', rgb: '59, 130, 246', tailwind: 'blue-500' },
|
|
170
|
+
DEFAULT: { hex: '#60a5fa', rgb: '96, 165, 250', tailwind: 'blue-400' },
|
|
171
|
+
dark: { hex: '#93c5fd', rgb: '147, 197, 253', tailwind: 'blue-300' },
|
|
172
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
generateNeutral(): SemanticColor {
|
|
177
|
+
return {
|
|
178
|
+
light: { hex: '#374151', rgb: '55, 65, 81', tailwind: 'gray-700' },
|
|
179
|
+
DEFAULT: { hex: '#9ca3af', rgb: '156, 163, 175', tailwind: 'gray-400' },
|
|
180
|
+
dark: { hex: '#d1d5db', rgb: '209, 213, 219', tailwind: 'gray-300' },
|
|
181
|
+
contrast: { hex: '#ffffff', rgb: '255, 255, 255', tailwind: 'white' },
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Color Token Factory
|
|
188
|
+
* Uses Strategy Pattern to generate colors based on theme
|
|
189
|
+
*/
|
|
190
|
+
export class ColorTokenFactory {
|
|
191
|
+
private strategy: ColorStrategy;
|
|
192
|
+
|
|
193
|
+
constructor(strategy: ColorStrategy) {
|
|
194
|
+
this.strategy = strategy;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Set color strategy
|
|
199
|
+
*/
|
|
200
|
+
setStrategy(strategy: ColorStrategy): void {
|
|
201
|
+
this.strategy = strategy;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Generate semantic color palette
|
|
206
|
+
*/
|
|
207
|
+
generatePalette(): Record<ColorRole, SemanticColor> {
|
|
208
|
+
return {
|
|
209
|
+
primary: this.strategy.generatePrimary(),
|
|
210
|
+
secondary: this.strategy.generateSecondary(),
|
|
211
|
+
success: this.strategy.generateSuccess(),
|
|
212
|
+
warning: this.strategy.generateWarning(),
|
|
213
|
+
error: this.strategy.generateError(),
|
|
214
|
+
info: this.strategy.generateInfo(),
|
|
215
|
+
neutral: this.strategy.generateNeutral(),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Light theme colors (default)
|
|
222
|
+
*/
|
|
223
|
+
const lightFactory = new ColorTokenFactory(new LightColorStrategy());
|
|
224
|
+
export const COLOR_TOKENS_LIGHT = lightFactory.generatePalette();
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Dark theme colors
|
|
228
|
+
*/
|
|
229
|
+
const darkFactory = new ColorTokenFactory(new DarkColorStrategy());
|
|
230
|
+
export const COLOR_TOKENS_DARK = darkFactory.generatePalette();
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Default color tokens (light theme)
|
|
234
|
+
*/
|
|
235
|
+
export const COLOR_TOKENS = COLOR_TOKENS_LIGHT;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Helper function to get color token
|
|
239
|
+
*/
|
|
240
|
+
export function getColor(role: ColorRole, shade: 'light' | 'DEFAULT' | 'dark' = 'DEFAULT'): ColorToken {
|
|
241
|
+
return COLOR_TOKENS[role][shade];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Helper function to get color as Tailwind class
|
|
246
|
+
*/
|
|
247
|
+
export function getColorClass(role: ColorRole, shade: 'light' | 'DEFAULT' | 'dark' = 'DEFAULT', type: 'text' | 'bg' | 'border' = 'text'): string {
|
|
248
|
+
const token = COLOR_TOKENS[role][shade];
|
|
249
|
+
return `${type}-${token.tailwind}`;
|
|
250
|
+
}
|
package/src/ui/tokens/sidebar.ts
CHANGED
|
@@ -25,9 +25,9 @@ export const SIDEBAR_TOKENS = {
|
|
|
25
25
|
spacing: {
|
|
26
26
|
itemPaddingX: 'px-4', // 16px horizontal padding for items
|
|
27
27
|
itemPaddingY: 'py-2', // 8px vertical padding for items
|
|
28
|
-
nestedIndent: 'pl-
|
|
29
|
-
nestedIndentLevel2: 'pl-
|
|
30
|
-
nestedIndentLevel3: 'pl-
|
|
28
|
+
nestedIndent: 'pl-6', // 24px for nested items (level 1)
|
|
29
|
+
nestedIndentLevel2: 'pl-10', // 40px for nested items (level 2)
|
|
30
|
+
nestedIndentLevel3: 'pl-14', // 56px for nested items (level 3)
|
|
31
31
|
groupTitlePadding: 'px-4 py-2', // Padding for group titles
|
|
32
32
|
iconMargin: 'mr-3', // 12px margin between icon and text
|
|
33
33
|
},
|
|
@@ -45,6 +45,12 @@ export const SIDEBAR_TOKENS = {
|
|
|
45
45
|
},
|
|
46
46
|
groupTitle: 'text-gray-500',
|
|
47
47
|
},
|
|
48
|
+
|
|
49
|
+
// Chevron (for collapsible groups)
|
|
50
|
+
chevron: {
|
|
51
|
+
size: 'h-3 w-3', // 12px (reduced from h-4 w-4)
|
|
52
|
+
color: 'text-gray-400', // Subtle gray color
|
|
53
|
+
},
|
|
48
54
|
} as const;
|
|
49
55
|
|
|
50
56
|
/**
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spacing Tokens
|
|
3
|
+
*
|
|
4
|
+
* Centralized spacing scale based on 4px base unit.
|
|
5
|
+
* Uses Factory Pattern for type-safe token creation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type SpacingScale =
|
|
9
|
+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 16 | 20 | 24 | 32 | 40 | 48 | 64 | 80 | 96;
|
|
10
|
+
|
|
11
|
+
export interface SpacingToken {
|
|
12
|
+
value: number;
|
|
13
|
+
rem: string;
|
|
14
|
+
px: string;
|
|
15
|
+
tailwind: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Spacing Token Factory
|
|
20
|
+
* Creates spacing tokens with consistent naming and values
|
|
21
|
+
*/
|
|
22
|
+
export class SpacingTokenFactory {
|
|
23
|
+
private static readonly BASE_UNIT = 4; // 4px base
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a spacing token from scale value
|
|
27
|
+
*/
|
|
28
|
+
static create(scale: SpacingScale): SpacingToken {
|
|
29
|
+
const px = scale * this.BASE_UNIT;
|
|
30
|
+
const rem = px / 16; // 16px = 1rem
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
value: px,
|
|
34
|
+
rem: `${rem}rem`,
|
|
35
|
+
px: `${px}px`,
|
|
36
|
+
tailwind: this.getTailwindClass(scale),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get Tailwind class for spacing value
|
|
42
|
+
*/
|
|
43
|
+
private static getTailwindClass(scale: SpacingScale): string {
|
|
44
|
+
const tailwindMap: Record<SpacingScale, string> = {
|
|
45
|
+
0: '0',
|
|
46
|
+
1: '1', // 4px
|
|
47
|
+
2: '2', // 8px
|
|
48
|
+
3: '3', // 12px
|
|
49
|
+
4: '4', // 16px
|
|
50
|
+
5: '5', // 20px
|
|
51
|
+
6: '6', // 24px
|
|
52
|
+
8: '8', // 32px
|
|
53
|
+
10: '10', // 40px
|
|
54
|
+
12: '12', // 48px
|
|
55
|
+
16: '16', // 64px
|
|
56
|
+
20: '20', // 80px
|
|
57
|
+
24: '24', // 96px
|
|
58
|
+
32: '32', // 128px
|
|
59
|
+
40: '40', // 160px
|
|
60
|
+
48: '48', // 192px
|
|
61
|
+
64: '64', // 256px
|
|
62
|
+
80: '80', // 320px
|
|
63
|
+
96: '96', // 384px
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return tailwindMap[scale] || String(scale);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Pre-defined spacing tokens
|
|
72
|
+
*/
|
|
73
|
+
export const SPACING_TOKENS = {
|
|
74
|
+
// Micro spacing (0-12px)
|
|
75
|
+
none: SpacingTokenFactory.create(0),
|
|
76
|
+
xs: SpacingTokenFactory.create(1), // 4px
|
|
77
|
+
sm: SpacingTokenFactory.create(2), // 8px
|
|
78
|
+
md: SpacingTokenFactory.create(3), // 12px
|
|
79
|
+
|
|
80
|
+
// Standard spacing (16-32px)
|
|
81
|
+
base: SpacingTokenFactory.create(4), // 16px
|
|
82
|
+
lg: SpacingTokenFactory.create(6), // 24px
|
|
83
|
+
xl: SpacingTokenFactory.create(8), // 32px
|
|
84
|
+
|
|
85
|
+
// Large spacing (40-64px)
|
|
86
|
+
'2xl': SpacingTokenFactory.create(10), // 40px
|
|
87
|
+
'3xl': SpacingTokenFactory.create(12), // 48px
|
|
88
|
+
'4xl': SpacingTokenFactory.create(16), // 64px
|
|
89
|
+
|
|
90
|
+
// Extra large spacing (80px+)
|
|
91
|
+
'5xl': SpacingTokenFactory.create(20), // 80px
|
|
92
|
+
'6xl': SpacingTokenFactory.create(24), // 96px
|
|
93
|
+
} as const;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Helper function to get spacing value
|
|
97
|
+
*/
|
|
98
|
+
export function getSpacing(scale: keyof typeof SPACING_TOKENS): SpacingToken {
|
|
99
|
+
return SPACING_TOKENS[scale];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Helper function to get spacing as Tailwind class
|
|
104
|
+
*/
|
|
105
|
+
export function getSpacingClass(scale: keyof typeof SPACING_TOKENS, direction: 'p' | 'm' | 'px' | 'mx' | 'py' | 'my' | 'pt' | 'mt' | 'pr' | 'mr' | 'pb' | 'mb' | 'pl' | 'ml' = 'p'): string {
|
|
106
|
+
const token = SPACING_TOKENS[scale];
|
|
107
|
+
const value = token.tailwind;
|
|
108
|
+
|
|
109
|
+
const prefixMap: Record<typeof direction, string> = {
|
|
110
|
+
p: 'p',
|
|
111
|
+
m: 'm',
|
|
112
|
+
px: 'px',
|
|
113
|
+
mx: 'mx',
|
|
114
|
+
py: 'py',
|
|
115
|
+
my: 'my',
|
|
116
|
+
pt: 'pt',
|
|
117
|
+
mt: 'mt',
|
|
118
|
+
pr: 'pr',
|
|
119
|
+
mr: 'mr',
|
|
120
|
+
pb: 'pb',
|
|
121
|
+
mb: 'mb',
|
|
122
|
+
pl: 'pl',
|
|
123
|
+
ml: 'ml',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return `${prefixMap[direction]}-${value}`;
|
|
127
|
+
}
|