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
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "atelier-ui-react",
3
+ "version": "1.0.2",
4
+ "description": "A modern, accessible React + TypeScript UI framework built with pure CSS design tokens.",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./src/index.ts",
8
+ "style": "./dist/index.css",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./src/index.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./styles.css": "./dist/index.css",
16
+ "./tokens": "./src/theme/tokens.css"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src"
21
+ ],
22
+ "sideEffects": [
23
+ "**/*.css"
24
+ ],
25
+ "scripts": {
26
+ "build": "npm run build:css && npm run build:js",
27
+ "build:css": "esbuild src/styles/globals.css --outfile=dist/index.css",
28
+ "build:js": "esbuild src/index.ts --bundle --format=esm --packages=external --outfile=dist/index.mjs && esbuild src/index.ts --bundle --format=cjs --packages=external --outfile=dist/index.cjs",
29
+ "lint": "tsc --noEmit",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "keywords": [
33
+ "react",
34
+ "ui",
35
+ "design-system",
36
+ "components",
37
+ "css-variables",
38
+ "typescript"
39
+ ],
40
+ "author": "Atelier UI Team",
41
+ "license": "MIT",
42
+ "peerDependencies": {
43
+ "react": "^18.0.0 || ^19.0.0",
44
+ "react-dom": "^18.0.0 || ^19.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "esbuild": "^0.25.0",
48
+ "typescript": "^5.0.0",
49
+ "@types/react": "^18.0.0 || ^19.0.0",
50
+ "@types/react-dom": "^18.0.0 || ^19.0.0"
51
+ }
52
+ }
@@ -0,0 +1,169 @@
1
+ .ui-btn {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: var(--ui-space-xs);
6
+ font-family: var(--ui-font-sans);
7
+ font-weight: var(--ui-font-weight-medium);
8
+ line-height: var(--ui-line-height-none);
9
+ white-space: nowrap;
10
+ border-radius: var(--ui-radius-md);
11
+ border: 1px solid transparent;
12
+ cursor: pointer;
13
+ user-select: none;
14
+ text-decoration: none;
15
+ position: relative;
16
+ transition:
17
+ background-color var(--ui-transition-fast),
18
+ border-color var(--ui-transition-fast),
19
+ color var(--ui-transition-fast),
20
+ box-shadow var(--ui-transition-fast),
21
+ transform 100ms ease;
22
+ }
23
+
24
+ .ui-btn:focus-visible {
25
+ outline: var(--ui-focus-ring-width) solid var(--ui-color-focus-ring);
26
+ outline-offset: var(--ui-focus-ring-offset);
27
+ }
28
+
29
+ .ui-btn:active:not(:disabled):not([aria-disabled="true"]) {
30
+ transform: scale(0.98);
31
+ }
32
+
33
+ /* Sizes */
34
+ .ui-btn--size-sm {
35
+ height: 32px;
36
+ padding: 0 12px;
37
+ font-size: var(--ui-font-size-xs);
38
+ gap: 6px;
39
+ border-radius: var(--ui-radius-sm);
40
+ }
41
+
42
+ .ui-btn--size-md {
43
+ height: 40px;
44
+ padding: 0 16px;
45
+ font-size: var(--ui-font-size-sm);
46
+ gap: 8px;
47
+ border-radius: var(--ui-radius-md);
48
+ }
49
+
50
+ .ui-btn--size-lg {
51
+ height: 48px;
52
+ padding: 0 24px;
53
+ font-size: var(--ui-font-size-md);
54
+ gap: 10px;
55
+ border-radius: var(--ui-radius-md);
56
+ }
57
+
58
+ /* Variant: Primary */
59
+ .ui-btn--variant-primary {
60
+ background-color: var(--ui-color-primary);
61
+ color: var(--ui-color-primary-fg);
62
+ box-shadow: var(--ui-shadow-xs);
63
+ }
64
+
65
+ .ui-btn--variant-primary:hover:not(:disabled):not([aria-disabled="true"]) {
66
+ background-color: var(--ui-color-primary-hover);
67
+ }
68
+
69
+ .ui-btn--variant-primary:active:not(:disabled):not([aria-disabled="true"]) {
70
+ background-color: var(--ui-color-primary-active);
71
+ }
72
+
73
+ /* Variant: Secondary */
74
+ .ui-btn--variant-secondary {
75
+ background-color: var(--ui-color-secondary);
76
+ color: var(--ui-color-secondary-fg);
77
+ border-color: var(--ui-color-border);
78
+ }
79
+
80
+ .ui-btn--variant-secondary:hover:not(:disabled):not([aria-disabled="true"]) {
81
+ background-color: var(--ui-color-secondary-hover);
82
+ border-color: var(--ui-color-border-hover);
83
+ }
84
+
85
+ .ui-btn--variant-secondary:active:not(:disabled):not([aria-disabled="true"]) {
86
+ background-color: var(--ui-color-secondary-active);
87
+ }
88
+
89
+ /* Variant: Outline */
90
+ .ui-btn--variant-outline {
91
+ background-color: transparent;
92
+ color: var(--ui-color-foreground);
93
+ border-color: var(--ui-color-border);
94
+ }
95
+
96
+ .ui-btn--variant-outline:hover:not(:disabled):not([aria-disabled="true"]) {
97
+ background-color: var(--ui-color-surface-hover);
98
+ border-color: var(--ui-color-border-strong);
99
+ }
100
+
101
+ .ui-btn--variant-outline:active:not(:disabled):not([aria-disabled="true"]) {
102
+ background-color: var(--ui-color-secondary);
103
+ }
104
+
105
+ /* Variant: Ghost */
106
+ .ui-btn--variant-ghost {
107
+ background-color: transparent;
108
+ color: var(--ui-color-foreground);
109
+ border-color: transparent;
110
+ }
111
+
112
+ .ui-btn--variant-ghost:hover:not(:disabled):not([aria-disabled="true"]) {
113
+ background-color: var(--ui-color-surface-hover);
114
+ }
115
+
116
+ .ui-btn--variant-ghost:active:not(:disabled):not([aria-disabled="true"]) {
117
+ background-color: var(--ui-color-secondary);
118
+ }
119
+
120
+ /* Variant: Danger */
121
+ .ui-btn--variant-danger {
122
+ background-color: var(--ui-color-danger);
123
+ color: var(--ui-color-danger-fg);
124
+ box-shadow: var(--ui-shadow-xs);
125
+ }
126
+
127
+ .ui-btn--variant-danger:hover:not(:disabled):not([aria-disabled="true"]) {
128
+ background-color: var(--ui-color-danger-hover);
129
+ }
130
+
131
+ .ui-btn--variant-danger:active:not(:disabled):not([aria-disabled="true"]) {
132
+ background-color: var(--ui-color-danger-active);
133
+ }
134
+
135
+ /* Modifiers */
136
+ .ui-btn--full-width {
137
+ width: 100%;
138
+ }
139
+
140
+ .ui-btn--disabled,
141
+ .ui-btn:disabled {
142
+ opacity: 0.5;
143
+ cursor: not-allowed;
144
+ pointer-events: none;
145
+ box-shadow: none;
146
+ }
147
+
148
+ .ui-btn--loading {
149
+ cursor: wait;
150
+ pointer-events: none;
151
+ }
152
+
153
+ /* Spinner inside button */
154
+ .ui-btn__spinner {
155
+ width: 1em;
156
+ height: 1em;
157
+ border: 2px solid currentColor;
158
+ border-right-color: transparent;
159
+ border-radius: 50%;
160
+ animation: ui-btn-spin 0.65s linear infinite;
161
+ display: inline-block;
162
+ flex-shrink: 0;
163
+ }
164
+
165
+ @keyframes ui-btn-spin {
166
+ to {
167
+ transform: rotate(360deg);
168
+ }
169
+ }
@@ -0,0 +1,57 @@
1
+ import React, { forwardRef } from 'react';
2
+ import type { ButtonProps } from './Button.types';
3
+ import { cx } from '../utils';
4
+ import './Button.css';
5
+
6
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
7
+ {
8
+ variant = 'primary',
9
+ size = 'md',
10
+ loading = false,
11
+ disabled = false,
12
+ iconLeft,
13
+ iconRight,
14
+ fullWidth = false,
15
+ as: Component = 'button',
16
+ type = 'button',
17
+ className,
18
+ children,
19
+ ...rest
20
+ },
21
+ ref
22
+ ) {
23
+ const isActuallyDisabled = disabled || loading;
24
+
25
+ const classes = cx(
26
+ 'ui-btn',
27
+ `ui-btn--variant-${variant}`,
28
+ `ui-btn--size-${size}`,
29
+ fullWidth && 'ui-btn--full-width',
30
+ isActuallyDisabled && 'ui-btn--disabled',
31
+ loading && 'ui-btn--loading',
32
+ className
33
+ );
34
+
35
+ const buttonProps = Component === 'button' ? { type, disabled: isActuallyDisabled } : { 'aria-disabled': isActuallyDisabled };
36
+
37
+ return React.createElement(
38
+ Component,
39
+ {
40
+ ref,
41
+ className: classes,
42
+ ...buttonProps,
43
+ ...rest,
44
+ },
45
+ <>
46
+ {loading ? (
47
+ <span className="ui-btn__spinner" aria-hidden="true" />
48
+ ) : (
49
+ iconLeft && <span className="ui-btn__icon-left">{iconLeft}</span>
50
+ )}
51
+ {children && <span className="ui-btn__label">{children}</span>}
52
+ {!loading && iconRight && <span className="ui-btn__icon-right">{iconRight}</span>}
53
+ </>
54
+ );
55
+ });
56
+
57
+ Button.displayName = 'Button';
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+
3
+ export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
4
+ export type ButtonSize = 'sm' | 'md' | 'lg';
5
+
6
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
7
+ variant?: ButtonVariant;
8
+ size?: ButtonSize;
9
+ loading?: boolean;
10
+ disabled?: boolean;
11
+ iconLeft?: React.ReactNode;
12
+ iconRight?: React.ReactNode;
13
+ fullWidth?: boolean;
14
+ as?: React.ElementType;
15
+ href?: string;
16
+ target?: string;
17
+ rel?: string;
18
+ children?: React.ReactNode;
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Button';
2
+ export * from './Button.types';
@@ -0,0 +1,73 @@
1
+ .ui-card {
2
+ box-sizing: border-box;
3
+ background-color: var(--ui-color-surface);
4
+ border-radius: var(--ui-radius-lg);
5
+ color: var(--ui-color-foreground);
6
+ position: relative;
7
+ transition:
8
+ background-color var(--ui-transition-fast),
9
+ border-color var(--ui-transition-fast),
10
+ box-shadow var(--ui-transition-fast),
11
+ transform var(--ui-transition-fast);
12
+ }
13
+
14
+ /* Variants */
15
+ .ui-card--variant-outlined {
16
+ border: 1px solid var(--ui-color-border);
17
+ box-shadow: none;
18
+ }
19
+
20
+ .ui-card--variant-elevated {
21
+ border: 1px solid var(--ui-color-border-subtle);
22
+ box-shadow: var(--ui-shadow-sm);
23
+ }
24
+
25
+ .ui-card--variant-flat {
26
+ background-color: var(--ui-color-surface-inset);
27
+ border: 1px solid transparent;
28
+ box-shadow: none;
29
+ }
30
+
31
+ /* Paddings */
32
+ .ui-card--padding-none { padding: 0; }
33
+ .ui-card--padding-xs { padding: var(--ui-space-xs); }
34
+ .ui-card--padding-sm { padding: var(--ui-space-sm); }
35
+ .ui-card--padding-md { padding: var(--ui-space-md); }
36
+ .ui-card--padding-lg { padding: var(--ui-space-lg); }
37
+ .ui-card--padding-xl { padding: var(--ui-space-xl); }
38
+
39
+ /* Interactive hover */
40
+ .ui-card--interactive {
41
+ cursor: pointer;
42
+ }
43
+
44
+ .ui-card--interactive:hover {
45
+ border-color: var(--ui-color-border-strong);
46
+ box-shadow: var(--ui-shadow-md);
47
+ transform: translateY(-1px);
48
+ }
49
+
50
+ .ui-card--interactive:active {
51
+ transform: translateY(0);
52
+ }
53
+
54
+ /* Sub-components */
55
+ .ui-card__header {
56
+ padding-bottom: var(--ui-space-sm);
57
+ border-bottom: 1px solid var(--ui-color-border-subtle);
58
+ margin-bottom: var(--ui-space-md);
59
+ }
60
+
61
+ .ui-card__body {
62
+ flex: 1 1 auto;
63
+ }
64
+
65
+ .ui-card__footer {
66
+ padding-top: var(--ui-space-sm);
67
+ border-top: 1px solid var(--ui-color-border-subtle);
68
+ margin-top: var(--ui-space-md);
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: flex-end;
72
+ gap: var(--ui-space-sm);
73
+ }
@@ -0,0 +1,79 @@
1
+ import React, { forwardRef } from 'react';
2
+ import type { CardProps, CardHeaderProps, CardBodyProps, CardFooterProps } from './Card.types';
3
+ import { cx } from '../utils';
4
+ import './Card.css';
5
+
6
+ const CardBase = forwardRef<HTMLDivElement, CardProps>(function Card(
7
+ {
8
+ as: Component = 'div',
9
+ variant = 'outlined',
10
+ padding = 'lg',
11
+ interactive = false,
12
+ className,
13
+ children,
14
+ ...rest
15
+ },
16
+ ref
17
+ ) {
18
+ const classes = cx(
19
+ 'ui-card',
20
+ `ui-card--variant-${variant}`,
21
+ `ui-card--padding-${padding}`,
22
+ interactive && 'ui-card--interactive',
23
+ className
24
+ );
25
+
26
+ return React.createElement(
27
+ Component,
28
+ {
29
+ ref,
30
+ className: classes,
31
+ ...rest,
32
+ },
33
+ children
34
+ );
35
+ });
36
+
37
+ const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(function CardHeader(
38
+ { className, children, ...rest },
39
+ ref
40
+ ) {
41
+ return (
42
+ <div ref={ref} className={cx('ui-card__header', className)} {...rest}>
43
+ {children}
44
+ </div>
45
+ );
46
+ });
47
+ CardHeader.displayName = 'Card.Header';
48
+
49
+ const CardBody = forwardRef<HTMLDivElement, CardBodyProps>(function CardBody(
50
+ { className, children, ...rest },
51
+ ref
52
+ ) {
53
+ return (
54
+ <div ref={ref} className={cx('ui-card__body', className)} {...rest}>
55
+ {children}
56
+ </div>
57
+ );
58
+ });
59
+ CardBody.displayName = 'Card.Body';
60
+
61
+ const CardFooter = forwardRef<HTMLDivElement, CardFooterProps>(function CardFooter(
62
+ { className, children, ...rest },
63
+ ref
64
+ ) {
65
+ return (
66
+ <div ref={ref} className={cx('ui-card__footer', className)} {...rest}>
67
+ {children}
68
+ </div>
69
+ );
70
+ });
71
+ CardFooter.displayName = 'Card.Footer';
72
+
73
+ export const Card = Object.assign(CardBase, {
74
+ Header: CardHeader,
75
+ Body: CardBody,
76
+ Footer: CardFooter,
77
+ });
78
+
79
+ Card.displayName = 'Card';
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import type { SpaceToken } from '../utils';
3
+
4
+ export type CardVariant = 'elevated' | 'outlined' | 'flat';
5
+ export type CardPadding = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
6
+
7
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
8
+ as?: React.ElementType;
9
+ variant?: CardVariant;
10
+ padding?: CardPadding;
11
+ interactive?: boolean;
12
+ children?: React.ReactNode;
13
+ }
14
+
15
+ export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
16
+ children?: React.ReactNode;
17
+ }
18
+
19
+ export interface CardBodyProps extends React.HTMLAttributes<HTMLDivElement> {
20
+ children?: React.ReactNode;
21
+ }
22
+
23
+ export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {
24
+ children?: React.ReactNode;
25
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Card';
2
+ export * from './Card.types';
@@ -0,0 +1,101 @@
1
+ .ui-checkbox-wrapper {
2
+ display: inline-flex;
3
+ align-items: flex-start;
4
+ gap: var(--ui-space-xs);
5
+ cursor: pointer;
6
+ user-select: none;
7
+ font-family: var(--ui-font-sans);
8
+ position: relative;
9
+ }
10
+
11
+ .ui-checkbox-wrapper--disabled {
12
+ cursor: not-allowed;
13
+ opacity: 0.6;
14
+ }
15
+
16
+ /* Hidden native input */
17
+ .ui-checkbox-input {
18
+ position: absolute;
19
+ opacity: 0;
20
+ width: 0;
21
+ height: 0;
22
+ margin: 0;
23
+ pointer-events: none;
24
+ }
25
+
26
+ /* Visual box */
27
+ .ui-checkbox-box {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ flex-shrink: 0;
32
+ border: 1px solid var(--ui-color-border-strong);
33
+ background-color: var(--ui-color-surface);
34
+ border-radius: var(--ui-radius-sm);
35
+ color: var(--ui-color-primary-fg);
36
+ transition:
37
+ background-color var(--ui-transition-fast),
38
+ border-color var(--ui-transition-fast),
39
+ box-shadow var(--ui-transition-fast);
40
+ }
41
+
42
+ /* Sizes */
43
+ .ui-checkbox-box--sm {
44
+ width: 16px;
45
+ height: 16px;
46
+ margin-top: 2px;
47
+ }
48
+
49
+ .ui-checkbox-box--md {
50
+ width: 18px;
51
+ height: 18px;
52
+ margin-top: 2px;
53
+ }
54
+
55
+ /* Checked / Indeterminate state */
56
+ .ui-checkbox-box--checked,
57
+ .ui-checkbox-box--indeterminate {
58
+ background-color: var(--ui-color-primary);
59
+ border-color: var(--ui-color-primary);
60
+ }
61
+
62
+ /* Focus */
63
+ .ui-checkbox-input:focus-visible + .ui-checkbox-box {
64
+ outline: var(--ui-focus-ring-width) solid var(--ui-color-focus-ring);
65
+ outline-offset: var(--ui-focus-ring-offset);
66
+ }
67
+
68
+ /* Error */
69
+ .ui-checkbox-box--error {
70
+ border-color: var(--ui-color-danger);
71
+ }
72
+
73
+ /* Icon svg */
74
+ .ui-checkbox-icon {
75
+ width: 12px;
76
+ height: 12px;
77
+ stroke: currentColor;
78
+ stroke-width: 2.5;
79
+ stroke-linecap: round;
80
+ stroke-linejoin: round;
81
+ fill: none;
82
+ }
83
+
84
+ /* Content labels */
85
+ .ui-checkbox-content {
86
+ display: flex;
87
+ flex-direction: column;
88
+ gap: 2px;
89
+ line-height: 1.4;
90
+ }
91
+
92
+ .ui-checkbox-label {
93
+ font-size: var(--ui-font-size-sm);
94
+ font-weight: var(--ui-font-weight-medium);
95
+ color: var(--ui-color-foreground);
96
+ }
97
+
98
+ .ui-checkbox-desc {
99
+ font-size: var(--ui-font-size-xs);
100
+ color: var(--ui-color-foreground-muted);
101
+ }
@@ -0,0 +1,85 @@
1
+ import React, { forwardRef, useEffect, useRef } from 'react';
2
+ import type { CheckboxProps } from './Checkbox.types';
3
+ import { cx } from '../utils';
4
+ import './Checkbox.css';
5
+
6
+ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox(
7
+ {
8
+ size = 'md',
9
+ label,
10
+ description,
11
+ indeterminate = false,
12
+ checked,
13
+ defaultChecked,
14
+ disabled = false,
15
+ error,
16
+ className,
17
+ onChange,
18
+ readOnly,
19
+ ...rest
20
+ },
21
+ forwardedRef
22
+ ) {
23
+ const innerRef = useRef<HTMLInputElement>(null);
24
+ const inputRef = (forwardedRef as React.RefObject<HTMLInputElement>) || innerRef;
25
+
26
+ useEffect(() => {
27
+ if (inputRef.current) {
28
+ inputRef.current.indeterminate = Boolean(indeterminate);
29
+ }
30
+ }, [indeterminate, inputRef]);
31
+
32
+ const isChecked = checked !== undefined ? checked : undefined;
33
+ const isReadOnly = readOnly !== undefined ? readOnly : (checked !== undefined && !onChange ? true : undefined);
34
+
35
+ const wrapperClasses = cx(
36
+ 'ui-checkbox-wrapper',
37
+ disabled && 'ui-checkbox-wrapper--disabled',
38
+ className
39
+ );
40
+
41
+ const boxClasses = cx(
42
+ 'ui-checkbox-box',
43
+ `ui-checkbox-box--${size}`,
44
+ (isChecked || defaultChecked) && 'ui-checkbox-box--checked',
45
+ indeterminate && 'ui-checkbox-box--indeterminate',
46
+ error && 'ui-checkbox-box--error'
47
+ );
48
+
49
+ return (
50
+ <label className={wrapperClasses}>
51
+ <input
52
+ ref={inputRef}
53
+ type="checkbox"
54
+ checked={checked}
55
+ defaultChecked={defaultChecked}
56
+ disabled={disabled}
57
+ readOnly={isReadOnly}
58
+ onChange={onChange}
59
+ className="ui-checkbox-input"
60
+ {...rest}
61
+ />
62
+
63
+ <span className={boxClasses} aria-hidden="true">
64
+ {indeterminate ? (
65
+ <svg className="ui-checkbox-icon" viewBox="0 0 24 24">
66
+ <line x1="5" y1="12" x2="19" y2="12" />
67
+ </svg>
68
+ ) : (
69
+ <svg className="ui-checkbox-icon" viewBox="0 0 24 24">
70
+ <polyline points="20 6 9 17 4 12" />
71
+ </svg>
72
+ )}
73
+ </span>
74
+
75
+ {(label || description) && (
76
+ <span className="ui-checkbox-content">
77
+ {label && <span className="ui-checkbox-label">{label}</span>}
78
+ {description && <span className="ui-checkbox-desc">{description}</span>}
79
+ </span>
80
+ )}
81
+ </label>
82
+ );
83
+ });
84
+
85
+ Checkbox.displayName = 'Checkbox';
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+
3
+ export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
4
+ size?: 'sm' | 'md';
5
+ label?: React.ReactNode;
6
+ description?: React.ReactNode;
7
+ indeterminate?: boolean;
8
+ error?: string | boolean;
9
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Checkbox';
2
+ export * from './Checkbox.types';