atelier-ui-react 1.0.2

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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -0
  3. package/dist/index.cjs +909 -0
  4. package/dist/index.css +962 -0
  5. package/dist/index.mjs +876 -0
  6. package/package.json +52 -0
  7. package/src/Button/Button.css +169 -0
  8. package/src/Button/Button.tsx +57 -0
  9. package/src/Button/Button.types.ts +19 -0
  10. package/src/Button/index.ts +2 -0
  11. package/src/Card/Card.css +73 -0
  12. package/src/Card/Card.tsx +79 -0
  13. package/src/Card/Card.types.ts +25 -0
  14. package/src/Card/index.ts +2 -0
  15. package/src/Checkbox/Checkbox.css +101 -0
  16. package/src/Checkbox/Checkbox.tsx +85 -0
  17. package/src/Checkbox/Checkbox.types.ts +9 -0
  18. package/src/Checkbox/index.ts +2 -0
  19. package/src/Divider/Divider.css +47 -0
  20. package/src/Divider/Divider.tsx +53 -0
  21. package/src/Divider/Divider.types.ts +12 -0
  22. package/src/Divider/index.ts +2 -0
  23. package/src/Heading/Heading.css +30 -0
  24. package/src/Heading/Heading.tsx +50 -0
  25. package/src/Heading/Heading.types.ts +16 -0
  26. package/src/Heading/index.ts +2 -0
  27. package/src/Icon/Icon.css +26 -0
  28. package/src/Icon/Icon.tsx +79 -0
  29. package/src/Icon/Icon.types.ts +13 -0
  30. package/src/Icon/index.ts +2 -0
  31. package/src/Input/Input.css +145 -0
  32. package/src/Input/Input.tsx +86 -0
  33. package/src/Input/Input.types.ts +15 -0
  34. package/src/Input/index.ts +2 -0
  35. package/src/Link/Link.css +50 -0
  36. package/src/Link/Link.tsx +64 -0
  37. package/src/Link/Link.types.ts +13 -0
  38. package/src/Link/index.ts +2 -0
  39. package/src/Select/Select.css +115 -0
  40. package/src/Select/Select.tsx +109 -0
  41. package/src/Select/Select.types.ts +19 -0
  42. package/src/Select/index.ts +2 -0
  43. package/src/Text/Text.css +49 -0
  44. package/src/Text/Text.tsx +46 -0
  45. package/src/Text/Text.types.ts +19 -0
  46. package/src/Text/index.ts +2 -0
  47. package/src/index.ts +34 -0
  48. package/src/layout/Container/Container.css +16 -0
  49. package/src/layout/Container/Container.tsx +51 -0
  50. package/src/layout/Container/Container.types.ts +12 -0
  51. package/src/layout/Container/index.ts +2 -0
  52. package/src/layout/Flex/Flex.css +34 -0
  53. package/src/layout/Flex/Flex.tsx +56 -0
  54. package/src/layout/Flex/Flex.types.ts +18 -0
  55. package/src/layout/Flex/index.ts +2 -0
  56. package/src/layout/Grid/Grid.css +18 -0
  57. package/src/layout/Grid/Grid.tsx +61 -0
  58. package/src/layout/Grid/Grid.types.ts +17 -0
  59. package/src/layout/Grid/index.ts +2 -0
  60. package/src/layout/Stack/Stack.css +50 -0
  61. package/src/layout/Stack/Stack.tsx +70 -0
  62. package/src/layout/Stack/Stack.types.ts +17 -0
  63. package/src/layout/Stack/index.ts +2 -0
  64. package/src/styles/globals.css +28 -0
  65. package/src/styles/reset.css +44 -0
  66. package/src/theme/ThemeProvider.tsx +83 -0
  67. package/src/theme/dark.css +62 -0
  68. package/src/theme/index.ts +2 -0
  69. package/src/theme/light.css +62 -0
  70. package/src/theme/theme.types.ts +19 -0
  71. package/src/theme/tokens.css +61 -0
  72. package/src/utils/index.ts +44 -0
@@ -0,0 +1,18 @@
1
+ .ui-grid {
2
+ display: grid;
3
+ box-sizing: border-box;
4
+ }
5
+
6
+ /* Align */
7
+ .ui-grid--align-start { align-items: start; }
8
+ .ui-grid--align-center { align-items: center; }
9
+ .ui-grid--align-end { align-items: end; }
10
+ .ui-grid--align-stretch { align-items: stretch; }
11
+
12
+ /* Justify */
13
+ .ui-grid--justify-start { justify-content: start; }
14
+ .ui-grid--justify-center { justify-content: center; }
15
+ .ui-grid--justify-end { justify-content: end; }
16
+ .ui-grid--justify-between { justify-content: space-between; }
17
+ .ui-grid--justify-around { justify-content: space-around; }
18
+ .ui-grid--justify-evenly { justify-content: space-evenly; }
@@ -0,0 +1,61 @@
1
+ import React, { forwardRef } from 'react';
2
+ import type { GridProps } from './Grid.types';
3
+ import { cx, getSpaceValue } from '../../utils';
4
+ import './Grid.css';
5
+
6
+ export const Grid = forwardRef<HTMLElement, GridProps>(function Grid(
7
+ {
8
+ as: Component = 'div',
9
+ columns,
10
+ minChildWidth,
11
+ gap = 'md',
12
+ rowGap,
13
+ columnGap,
14
+ align,
15
+ justify,
16
+ className,
17
+ style,
18
+ children,
19
+ ...rest
20
+ },
21
+ ref
22
+ ) {
23
+ let gridTemplateColumns: string | undefined;
24
+
25
+ if (minChildWidth) {
26
+ const minWidth = typeof minChildWidth === 'number' ? `${minChildWidth}px` : minChildWidth;
27
+ gridTemplateColumns = `repeat(auto-fit, minmax(${minWidth}, 1fr))`;
28
+ } else if (typeof columns === 'number') {
29
+ gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`;
30
+ } else if (typeof columns === 'string') {
31
+ gridTemplateColumns = columns;
32
+ }
33
+
34
+ const classes = cx(
35
+ 'ui-grid',
36
+ align && `ui-grid--align-${align}`,
37
+ justify && `ui-grid--justify-${justify}`,
38
+ className
39
+ );
40
+
41
+ const combinedStyle: React.CSSProperties = {
42
+ gridTemplateColumns,
43
+ gap: getSpaceValue(gap),
44
+ rowGap: getSpaceValue(rowGap),
45
+ columnGap: getSpaceValue(columnGap),
46
+ ...style,
47
+ };
48
+
49
+ return React.createElement(
50
+ Component,
51
+ {
52
+ ref,
53
+ className: classes,
54
+ style: combinedStyle,
55
+ ...rest,
56
+ },
57
+ children
58
+ );
59
+ });
60
+
61
+ Grid.displayName = 'Grid';
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import type { SpaceToken } from '../../utils';
3
+
4
+ export type GridAlign = 'start' | 'center' | 'end' | 'stretch';
5
+ export type GridJustify = 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
6
+
7
+ export interface GridProps extends React.HTMLAttributes<HTMLElement> {
8
+ as?: React.ElementType;
9
+ columns?: number | string;
10
+ minChildWidth?: number | string;
11
+ gap?: SpaceToken;
12
+ rowGap?: SpaceToken;
13
+ columnGap?: SpaceToken;
14
+ align?: GridAlign;
15
+ justify?: GridJustify;
16
+ children?: React.ReactNode;
17
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Grid';
2
+ export * from './Grid.types';
@@ -0,0 +1,50 @@
1
+ .ui-stack {
2
+ display: flex;
3
+ box-sizing: border-box;
4
+ }
5
+
6
+ /* Direction */
7
+ .ui-stack--vertical {
8
+ flex-direction: column;
9
+ }
10
+
11
+ .ui-stack--horizontal {
12
+ flex-direction: row;
13
+ }
14
+
15
+ /* Align */
16
+ .ui-stack--align-start { align-items: flex-start; }
17
+ .ui-stack--align-center { align-items: center; }
18
+ .ui-stack--align-end { align-items: flex-end; }
19
+ .ui-stack--align-stretch { align-items: stretch; }
20
+ .ui-stack--align-baseline { align-items: baseline; }
21
+
22
+ /* Justify */
23
+ .ui-stack--justify-start { justify-content: flex-start; }
24
+ .ui-stack--justify-center { justify-content: center; }
25
+ .ui-stack--justify-end { justify-content: flex-end; }
26
+ .ui-stack--justify-between { justify-content: space-between; }
27
+ .ui-stack--justify-around { justify-content: space-around; }
28
+ .ui-stack--justify-evenly { justify-content: space-evenly; }
29
+
30
+ .ui-stack--wrap {
31
+ flex-wrap: wrap;
32
+ }
33
+
34
+ /* Built-in divider element */
35
+ .ui-stack-divider {
36
+ align-self: stretch;
37
+ background-color: var(--ui-color-border);
38
+ border: none;
39
+ margin: 0;
40
+ }
41
+
42
+ .ui-stack--vertical > .ui-stack-divider {
43
+ height: 1px;
44
+ width: 100%;
45
+ }
46
+
47
+ .ui-stack--horizontal > .ui-stack-divider {
48
+ width: 1px;
49
+ height: 100%;
50
+ }
@@ -0,0 +1,70 @@
1
+ import React, { forwardRef, Children } from 'react';
2
+ import type { StackProps } from './Stack.types';
3
+ import { cx, getSpaceValue } from '../../utils';
4
+ import './Stack.css';
5
+
6
+ export const Stack = forwardRef<HTMLElement, StackProps>(function Stack(
7
+ {
8
+ as: Component = 'div',
9
+ direction = 'vertical',
10
+ gap = 'md',
11
+ align,
12
+ justify,
13
+ wrap = false,
14
+ divider,
15
+ className,
16
+ style,
17
+ children,
18
+ ...rest
19
+ },
20
+ ref
21
+ ) {
22
+ const resolvedGap = gap === 'none' ? '0px' : getSpaceValue(gap);
23
+
24
+ const classes = cx(
25
+ 'ui-stack',
26
+ `ui-stack--${direction}`,
27
+ align && `ui-stack--align-${align}`,
28
+ justify && `ui-stack--justify-${justify}`,
29
+ wrap && 'ui-stack--wrap',
30
+ className
31
+ );
32
+
33
+ const combinedStyle: React.CSSProperties = {
34
+ gap: resolvedGap,
35
+ ...style,
36
+ };
37
+
38
+ let renderedChildren = children;
39
+ if (divider) {
40
+ const validChildren = Children.toArray(children).filter(Boolean);
41
+ renderedChildren = validChildren.map((child, index) => {
42
+ const isLast = index === validChildren.length - 1;
43
+ return (
44
+ <React.Fragment key={index}>
45
+ {child}
46
+ {!isLast && (
47
+ React.isValidElement(divider) ? (
48
+ divider
49
+ ) : (
50
+ <div className="ui-stack-divider" role="separator" />
51
+ )
52
+ )}
53
+ </React.Fragment>
54
+ );
55
+ });
56
+ }
57
+
58
+ return React.createElement(
59
+ Component,
60
+ {
61
+ ref,
62
+ className: classes,
63
+ style: combinedStyle,
64
+ ...rest,
65
+ },
66
+ renderedChildren
67
+ );
68
+ });
69
+
70
+ Stack.displayName = 'Stack';
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import type { SpaceToken } from '../../utils';
3
+
4
+ export type StackDirection = 'vertical' | 'horizontal';
5
+ export type StackAlign = 'start' | 'center' | 'end' | 'stretch' | 'baseline';
6
+ export type StackJustify = 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
7
+
8
+ export interface StackProps extends React.HTMLAttributes<HTMLElement> {
9
+ as?: React.ElementType;
10
+ direction?: StackDirection;
11
+ gap?: SpaceToken | 'none';
12
+ align?: StackAlign;
13
+ justify?: StackJustify;
14
+ wrap?: boolean;
15
+ divider?: React.ReactNode;
16
+ children?: React.ReactNode;
17
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Stack';
2
+ export * from './Stack.types';
@@ -0,0 +1,28 @@
1
+ @import "./reset.css";
2
+ @import "../theme/tokens.css";
3
+ @import "../theme/light.css";
4
+ @import "../theme/dark.css";
5
+
6
+ /* Base Body Application */
7
+ html, body {
8
+ font-family: var(--ui-font-sans);
9
+ font-size: var(--ui-font-size-md);
10
+ line-height: var(--ui-line-height-normal);
11
+ color: var(--ui-color-foreground);
12
+ background-color: var(--ui-color-background);
13
+ -webkit-font-smoothing: antialiased;
14
+ -moz-osx-font-smoothing: grayscale;
15
+ text-rendering: optimizeLegibility;
16
+ transition: background-color var(--ui-transition-base), color var(--ui-transition-base);
17
+ }
18
+
19
+ ::selection {
20
+ background-color: var(--ui-color-primary);
21
+ color: var(--ui-color-primary-fg);
22
+ }
23
+
24
+ /* Accessibility Focus Visible standard fallback */
25
+ :focus-visible {
26
+ outline: var(--ui-focus-ring-width) solid var(--ui-color-focus-ring);
27
+ outline-offset: var(--ui-focus-ring-offset);
28
+ }
@@ -0,0 +1,44 @@
1
+ /* Atelier UI — Modern Scoped CSS Reset */
2
+ *, *::before, *::after {
3
+ box-sizing: border-box;
4
+ }
5
+
6
+ html {
7
+ -webkit-text-size-adjust: 100%;
8
+ tab-size: 4;
9
+ }
10
+
11
+ body {
12
+ margin: 0;
13
+ line-height: inherit;
14
+ }
15
+
16
+ hr {
17
+ height: 0;
18
+ color: inherit;
19
+ border-top-width: 1px;
20
+ }
21
+
22
+ button, input, optgroup, select, textarea {
23
+ font-family: inherit;
24
+ font-size: 100%;
25
+ font-weight: inherit;
26
+ line-height: inherit;
27
+ color: inherit;
28
+ margin: 0;
29
+ padding: 0;
30
+ }
31
+
32
+ button, select {
33
+ text-transform: none;
34
+ }
35
+
36
+ button, [type='button'], [type='reset'], [type='submit'] {
37
+ -webkit-appearance: button;
38
+ background-color: transparent;
39
+ background-image: none;
40
+ }
41
+
42
+ input::placeholder, textarea::placeholder {
43
+ opacity: 1;
44
+ }
@@ -0,0 +1,83 @@
1
+ import React, { createContext, useContext, useEffect, useState, useMemo } from 'react';
2
+ import type { ThemeContextValue, ThemeMode, ThemeProviderProps, ResolvedTheme } from './theme.types';
3
+
4
+ const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
5
+
6
+ export function ThemeProvider({
7
+ children,
8
+ defaultTheme = 'light',
9
+ storageKey = 'atelier-ui-theme',
10
+ attribute = 'data-theme',
11
+ enableSystem = true,
12
+ }: ThemeProviderProps) {
13
+ const [theme, setThemeState] = useState<ThemeMode>(() => {
14
+ if (typeof window === 'undefined') return defaultTheme;
15
+ try {
16
+ const stored = localStorage.getItem(storageKey);
17
+ if (stored === 'light' || stored === 'dark' || stored === 'system') {
18
+ return stored;
19
+ }
20
+ } catch {
21
+ // Ignore localStorage read errors
22
+ }
23
+ return defaultTheme;
24
+ });
25
+
26
+ const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>('light');
27
+
28
+ useEffect(() => {
29
+ const root = document.documentElement;
30
+
31
+ const getSystemTheme = (): ResolvedTheme => {
32
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
33
+ };
34
+
35
+ const active = theme === 'system' && enableSystem ? getSystemTheme() : (theme === 'dark' ? 'dark' : 'light');
36
+ setResolvedTheme(active);
37
+ root.setAttribute(attribute, active);
38
+
39
+ if (theme === 'system' && enableSystem) {
40
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
41
+ const listener = (e: MediaQueryListEvent) => {
42
+ const nextTheme = e.matches ? 'dark' : 'light';
43
+ setResolvedTheme(nextTheme);
44
+ root.setAttribute(attribute, nextTheme);
45
+ };
46
+ mediaQuery.addEventListener('change', listener);
47
+ return () => mediaQuery.removeEventListener('change', listener);
48
+ }
49
+ }, [theme, attribute, enableSystem]);
50
+
51
+ const setTheme = (newTheme: ThemeMode) => {
52
+ try {
53
+ localStorage.setItem(storageKey, newTheme);
54
+ } catch {
55
+ // Ignore localStorage write errors
56
+ }
57
+ setThemeState(newTheme);
58
+ };
59
+
60
+ const toggleTheme = () => {
61
+ setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
62
+ };
63
+
64
+ const value = useMemo(
65
+ () => ({
66
+ theme,
67
+ resolvedTheme,
68
+ setTheme,
69
+ toggleTheme,
70
+ }),
71
+ [theme, resolvedTheme]
72
+ );
73
+
74
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
75
+ }
76
+
77
+ export function useTheme(): ThemeContextValue {
78
+ const context = useContext(ThemeContext);
79
+ if (!context) {
80
+ throw new Error('useTheme must be used within an Atelier UI ThemeProvider');
81
+ }
82
+ return context;
83
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Atelier UI — Dark Theme
3
+ * Refined deep obsidian graphite theme with zero eye fatigue and balanced contrast
4
+ */
5
+ [data-theme="dark"] {
6
+ --ui-color-background: #090a0b;
7
+ --ui-color-surface: #121316;
8
+ --ui-color-surface-hover: #191b1f;
9
+ --ui-color-surface-raised: #181a1e;
10
+ --ui-color-surface-inset: #0e0f12;
11
+
12
+ --ui-color-foreground: #f4f4f5;
13
+ --ui-color-foreground-muted: #a1a1aa;
14
+ --ui-color-foreground-subtle: #71717a;
15
+
16
+ --ui-color-border: #23262b;
17
+ --ui-color-border-hover: #32363d;
18
+ --ui-color-border-subtle: #191c20;
19
+ --ui-color-border-strong: #454a54;
20
+
21
+ /* Brand / Primary */
22
+ --ui-color-primary: #f4f4f5;
23
+ --ui-color-primary-hover: #e4e4e7;
24
+ --ui-color-primary-active: #d4d4d8;
25
+ --ui-color-primary-fg: #090a0b;
26
+
27
+ /* Secondary */
28
+ --ui-color-secondary: #1a1c20;
29
+ --ui-color-secondary-hover: #24272d;
30
+ --ui-color-secondary-active: #2e3239;
31
+ --ui-color-secondary-fg: #f4f4f5;
32
+
33
+ /* Accent */
34
+ --ui-color-accent: #3b82f6;
35
+ --ui-color-accent-hover: #60a5fa;
36
+ --ui-color-accent-fg: #ffffff;
37
+ --ui-color-accent-subtle: #172554;
38
+
39
+ /* Status Colors */
40
+ --ui-color-success: #22c55e;
41
+ --ui-color-success-hover: #4ade80;
42
+ --ui-color-success-fg: #052e16;
43
+ --ui-color-success-subtle: #052e16;
44
+ --ui-color-success-border: #14532d;
45
+
46
+ --ui-color-warning: #f59e0b;
47
+ --ui-color-warning-hover: #fbbf24;
48
+ --ui-color-warning-fg: #451a03;
49
+ --ui-color-warning-subtle: #451a03;
50
+ --ui-color-warning-border: #78350f;
51
+
52
+ --ui-color-danger: #ef4444;
53
+ --ui-color-danger-hover: #f87171;
54
+ --ui-color-danger-active: #dc2626;
55
+ --ui-color-danger-fg: #ffffff;
56
+ --ui-color-danger-subtle: #450a0a;
57
+ --ui-color-danger-border: #7f1d1d;
58
+
59
+ /* Focus Ring */
60
+ --ui-color-focus-ring: rgba(59, 130, 246, 0.45);
61
+ --ui-color-focus-border: #60a5fa;
62
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ThemeProvider';
2
+ export * from './theme.types';
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Atelier UI — Light Theme
3
+ * Crisp, warm-tinted modern paper neutral aesthetic with high contrast
4
+ */
5
+ :root, [data-theme="light"] {
6
+ --ui-color-background: #fafaf9;
7
+ --ui-color-surface: #ffffff;
8
+ --ui-color-surface-hover: #f5f5f4;
9
+ --ui-color-surface-raised: #ffffff;
10
+ --ui-color-surface-inset: #f5f5f4;
11
+
12
+ --ui-color-foreground: #1c1917;
13
+ --ui-color-foreground-muted: #78716c;
14
+ --ui-color-foreground-subtle: #a8a29e;
15
+
16
+ --ui-color-border: #e7e5e4;
17
+ --ui-color-border-hover: #d6d3d1;
18
+ --ui-color-border-subtle: #f0eeed;
19
+ --ui-color-border-strong: #a8a29e;
20
+
21
+ /* Brand / Primary */
22
+ --ui-color-primary: #18181b;
23
+ --ui-color-primary-hover: #27272a;
24
+ --ui-color-primary-active: #09090b;
25
+ --ui-color-primary-fg: #ffffff;
26
+
27
+ /* Secondary */
28
+ --ui-color-secondary: #f4f4f5;
29
+ --ui-color-secondary-hover: #e4e4e7;
30
+ --ui-color-secondary-active: #d4d4d8;
31
+ --ui-color-secondary-fg: #18181b;
32
+
33
+ /* Accent */
34
+ --ui-color-accent: #2563eb;
35
+ --ui-color-accent-hover: #1d4ed8;
36
+ --ui-color-accent-fg: #ffffff;
37
+ --ui-color-accent-subtle: #eff6ff;
38
+
39
+ /* Status Colors */
40
+ --ui-color-success: #16a34a;
41
+ --ui-color-success-hover: #15803d;
42
+ --ui-color-success-fg: #ffffff;
43
+ --ui-color-success-subtle: #f0fdf4;
44
+ --ui-color-success-border: #bbf7d0;
45
+
46
+ --ui-color-warning: #d97706;
47
+ --ui-color-warning-hover: #b45309;
48
+ --ui-color-warning-fg: #ffffff;
49
+ --ui-color-warning-subtle: #fffbeb;
50
+ --ui-color-warning-border: #fde68a;
51
+
52
+ --ui-color-danger: #dc2626;
53
+ --ui-color-danger-hover: #b91c1c;
54
+ --ui-color-danger-active: #991b1b;
55
+ --ui-color-danger-fg: #ffffff;
56
+ --ui-color-danger-subtle: #fef2f2;
57
+ --ui-color-danger-border: #fecaca;
58
+
59
+ /* Focus Ring */
60
+ --ui-color-focus-ring: rgba(37, 99, 235, 0.35);
61
+ --ui-color-focus-border: #2563eb;
62
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+
3
+ export type ThemeMode = 'light' | 'dark' | 'system';
4
+ export type ResolvedTheme = 'light' | 'dark';
5
+
6
+ export interface ThemeContextValue {
7
+ theme: ThemeMode;
8
+ resolvedTheme: ResolvedTheme;
9
+ setTheme: (theme: ThemeMode) => void;
10
+ toggleTheme: () => void;
11
+ }
12
+
13
+ export interface ThemeProviderProps {
14
+ children: React.ReactNode;
15
+ defaultTheme?: ThemeMode;
16
+ storageKey?: string;
17
+ attribute?: string;
18
+ enableSystem?: boolean;
19
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Atelier UI — Design Tokens
3
+ * Scalable, consistent, unit-harmonized scales
4
+ */
5
+ :root {
6
+ /* Typography Scale */
7
+ --ui-font-sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
8
+ --ui-font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
9
+
10
+ --ui-font-size-xs: 0.75rem; /* 12px */
11
+ --ui-font-size-sm: 0.875rem; /* 14px */
12
+ --ui-font-size-md: 1rem; /* 16px */
13
+ --ui-font-size-lg: 1.125rem; /* 18px */
14
+ --ui-font-size-xl: 1.25rem; /* 20px */
15
+ --ui-font-size-2xl: 1.5rem; /* 24px */
16
+ --ui-font-size-3xl: 1.875rem; /* 30px */
17
+ --ui-font-size-4xl: 2.25rem; /* 36px */
18
+
19
+ --ui-font-weight-normal: 400;
20
+ --ui-font-weight-medium: 500;
21
+ --ui-font-weight-semibold: 600;
22
+ --ui-font-weight-bold: 700;
23
+
24
+ --ui-line-height-none: 1;
25
+ --ui-line-height-tight: 1.25;
26
+ --ui-line-height-normal: 1.5;
27
+ --ui-line-height-relaxed: 1.65;
28
+
29
+ /* Spacing Scale */
30
+ --ui-space-2xs: 0.25rem; /* 4px */
31
+ --ui-space-xs: 0.5rem; /* 8px */
32
+ --ui-space-sm: 0.75rem; /* 12px */
33
+ --ui-space-md: 1rem; /* 16px */
34
+ --ui-space-lg: 1.5rem; /* 24px */
35
+ --ui-space-xl: 2rem; /* 32px */
36
+ --ui-space-2xl: 3rem; /* 48px */
37
+ --ui-space-3xl: 4rem; /* 64px */
38
+
39
+ /* Border Radius */
40
+ --ui-radius-none: 0px;
41
+ --ui-radius-sm: 6px;
42
+ --ui-radius-md: 10px;
43
+ --ui-radius-lg: 14px;
44
+ --ui-radius-full: 9999px;
45
+
46
+ /* Shadows */
47
+ --ui-shadow-none: none;
48
+ --ui-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04);
49
+ --ui-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
50
+ --ui-shadow-md: 0 4px 12px -2px rgba(0, 0, 0, 0.08), 0 2px 6px -1px rgba(0, 0, 0, 0.04);
51
+ --ui-shadow-lg: 0 12px 24px -4px rgba(0, 0, 0, 0.1), 0 4px 8px -2px rgba(0, 0, 0, 0.04);
52
+
53
+ /* Transitions */
54
+ --ui-transition-fast: 150ms cubic-bezier(0.16, 1, 0.3, 1);
55
+ --ui-transition-base: 220ms cubic-bezier(0.16, 1, 0.3, 1);
56
+ --ui-transition-transform: transform 180ms cubic-bezier(0.16, 1, 0.3, 1);
57
+
58
+ /* Focus Ring */
59
+ --ui-focus-ring-width: 3px;
60
+ --ui-focus-ring-offset: 2px;
61
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Atelier UI — Internal Utilities
3
+ */
4
+
5
+ export type ClassValue = string | number | bigint | boolean | undefined | null | { [key: string]: any } | ClassValue[];
6
+
7
+ /**
8
+ * Lightweight classNames / cx utility with zero dependencies
9
+ */
10
+ export function cx(...classes: ClassValue[]): string {
11
+ const result: string[] = [];
12
+
13
+ for (const item of classes) {
14
+ if (!item) continue;
15
+
16
+ if (typeof item === 'string' || typeof item === 'number') {
17
+ result.push(String(item));
18
+ } else if (Array.isArray(item)) {
19
+ const inner = cx(...item);
20
+ if (inner) result.push(inner);
21
+ } else if (typeof item === 'object') {
22
+ for (const [key, value] of Object.entries(item)) {
23
+ if (value) result.push(key);
24
+ }
25
+ }
26
+ }
27
+
28
+ return result.join(' ');
29
+ }
30
+
31
+ /**
32
+ * Maps space scale token values to css variable or raw css length
33
+ */
34
+ export type SpaceToken = '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | number | string;
35
+
36
+ export function getSpaceValue(val?: SpaceToken): string | undefined {
37
+ if (val === undefined) return undefined;
38
+ if (typeof val === 'number') return `${val}px`;
39
+ const tokenList = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'];
40
+ if (tokenList.includes(val)) {
41
+ return `var(--ui-space-${val})`;
42
+ }
43
+ return val;
44
+ }