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,64 @@
1
+ import React, { forwardRef } from 'react';
2
+ import type { LinkProps } from './Link.types';
3
+ import { cx } from '../utils';
4
+ import './Link.css';
5
+
6
+ export const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
7
+ {
8
+ variant = 'default',
9
+ size = 'md',
10
+ external = false,
11
+ iconLeft,
12
+ iconRight,
13
+ className,
14
+ target,
15
+ rel,
16
+ children,
17
+ ...rest
18
+ },
19
+ ref
20
+ ) {
21
+ const classes = cx(
22
+ 'ui-link',
23
+ `ui-link--variant-${variant}`,
24
+ `ui-link--size-${size}`,
25
+ className
26
+ );
27
+
28
+ const resolvedTarget = external ? '_blank' : target;
29
+ const resolvedRel = external ? (rel ? `${rel} noopener noreferrer` : 'noopener noreferrer') : rel;
30
+
31
+ return (
32
+ <a
33
+ ref={ref}
34
+ className={classes}
35
+ target={resolvedTarget}
36
+ rel={resolvedRel}
37
+ {...rest}
38
+ >
39
+ {iconLeft && <span className="ui-link__icon-left">{iconLeft}</span>}
40
+ <span>{children}</span>
41
+ {iconRight && <span className="ui-link__icon-right">{iconRight}</span>}
42
+ {external && !iconRight && (
43
+ <svg
44
+ width="12"
45
+ height="12"
46
+ viewBox="0 0 24 24"
47
+ fill="none"
48
+ stroke="currentColor"
49
+ strokeWidth="2"
50
+ strokeLinecap="round"
51
+ strokeLinejoin="round"
52
+ className="ui-link__external-icon"
53
+ aria-hidden="true"
54
+ >
55
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
56
+ <polyline points="15 3 21 3 21 9" />
57
+ <line x1="10" y1="14" x2="21" y2="3" />
58
+ </svg>
59
+ )}
60
+ </a>
61
+ );
62
+ });
63
+
64
+ Link.displayName = 'Link';
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+
3
+ export type LinkVariant = 'default' | 'subtle' | 'underline';
4
+ export type LinkSize = 'sm' | 'md' | 'lg';
5
+
6
+ export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
7
+ variant?: LinkVariant;
8
+ size?: LinkSize;
9
+ external?: boolean;
10
+ iconLeft?: React.ReactNode;
11
+ iconRight?: React.ReactNode;
12
+ children?: React.ReactNode;
13
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Link';
2
+ export * from './Link.types';
@@ -0,0 +1,115 @@
1
+ .ui-select-wrapper {
2
+ display: inline-flex;
3
+ flex-direction: column;
4
+ gap: var(--ui-space-2xs);
5
+ box-sizing: border-box;
6
+ font-family: var(--ui-font-sans);
7
+ }
8
+
9
+ .ui-select-wrapper--full-width {
10
+ width: 100%;
11
+ }
12
+
13
+ /* Label */
14
+ .ui-select-label {
15
+ font-size: var(--ui-font-size-xs);
16
+ font-weight: var(--ui-font-weight-medium);
17
+ color: var(--ui-color-foreground);
18
+ user-select: none;
19
+ }
20
+
21
+ .ui-select-label--disabled {
22
+ color: var(--ui-color-foreground-muted);
23
+ }
24
+
25
+ /* Select Container */
26
+ .ui-select-container {
27
+ position: relative;
28
+ display: flex;
29
+ align-items: center;
30
+ width: 100%;
31
+ }
32
+
33
+ /* Native select styled cleanly */
34
+ .ui-select {
35
+ width: 100%;
36
+ font-family: var(--ui-font-sans);
37
+ color: var(--ui-color-foreground);
38
+ background-color: var(--ui-color-surface);
39
+ border: 1px solid var(--ui-color-border);
40
+ border-radius: var(--ui-radius-md);
41
+ appearance: none;
42
+ -webkit-appearance: none;
43
+ padding-right: 36px;
44
+ cursor: pointer;
45
+ outline: none;
46
+ transition:
47
+ border-color var(--ui-transition-fast),
48
+ box-shadow var(--ui-transition-fast),
49
+ background-color var(--ui-transition-fast);
50
+ }
51
+
52
+ .ui-select:focus {
53
+ border-color: var(--ui-color-focus-border);
54
+ box-shadow: 0 0 0 var(--ui-focus-ring-width) var(--ui-color-focus-ring);
55
+ }
56
+
57
+ /* Sizes */
58
+ .ui-select--size-sm {
59
+ height: 32px;
60
+ padding-left: 10px;
61
+ font-size: var(--ui-font-size-xs);
62
+ border-radius: var(--ui-radius-sm);
63
+ }
64
+
65
+ .ui-select--size-md {
66
+ height: 40px;
67
+ padding-left: 12px;
68
+ font-size: var(--ui-font-size-sm);
69
+ border-radius: var(--ui-radius-md);
70
+ }
71
+
72
+ .ui-select--size-lg {
73
+ height: 48px;
74
+ padding-left: 16px;
75
+ font-size: var(--ui-font-size-md);
76
+ border-radius: var(--ui-radius-md);
77
+ }
78
+
79
+ /* States */
80
+ .ui-select:disabled {
81
+ background-color: var(--ui-color-surface-inset);
82
+ color: var(--ui-color-foreground-muted);
83
+ border-color: var(--ui-color-border-subtle);
84
+ cursor: not-allowed;
85
+ opacity: 0.7;
86
+ }
87
+
88
+ .ui-select--error,
89
+ .ui-select--error:focus {
90
+ border-color: var(--ui-color-danger);
91
+ box-shadow: 0 0 0 var(--ui-focus-ring-width) rgba(220, 38, 38, 0.2);
92
+ }
93
+
94
+ /* Chevron arrow */
95
+ .ui-select-icon {
96
+ position: absolute;
97
+ right: 12px;
98
+ pointer-events: none;
99
+ color: var(--ui-color-foreground-muted);
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ }
104
+
105
+ /* Helper / Error */
106
+ .ui-select-helper {
107
+ font-size: var(--ui-font-size-xs);
108
+ color: var(--ui-color-foreground-muted);
109
+ line-height: 1.4;
110
+ }
111
+
112
+ .ui-select-helper--error {
113
+ color: var(--ui-color-danger);
114
+ font-weight: var(--ui-font-weight-medium);
115
+ }
@@ -0,0 +1,109 @@
1
+ import React, { forwardRef, useId } from 'react';
2
+ import type { SelectProps } from './Select.types';
3
+ import { cx } from '../utils';
4
+ import './Select.css';
5
+
6
+ export const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select(
7
+ {
8
+ options,
9
+ placeholder,
10
+ size = 'md',
11
+ label,
12
+ helperText,
13
+ error,
14
+ fullWidth = false,
15
+ disabled = false,
16
+ id: explicitId,
17
+ className,
18
+ children,
19
+ value,
20
+ defaultValue,
21
+ ...rest
22
+ },
23
+ ref
24
+ ) {
25
+ const generatedId = useId();
26
+ const id = explicitId || generatedId;
27
+ const helperId = `${id}-helper`;
28
+ const hasError = Boolean(error);
29
+ const errorMessage = typeof error === 'string' ? error : undefined;
30
+
31
+ const wrapperClasses = cx(
32
+ 'ui-select-wrapper',
33
+ fullWidth && 'ui-select-wrapper--full-width'
34
+ );
35
+
36
+ const selectClasses = cx(
37
+ 'ui-select',
38
+ `ui-select--size-${size}`,
39
+ hasError && 'ui-select--error',
40
+ className
41
+ );
42
+
43
+ return (
44
+ <div className={wrapperClasses}>
45
+ {label && (
46
+ <label
47
+ htmlFor={id}
48
+ className={cx('ui-select-label', disabled && 'ui-select-label--disabled')}
49
+ >
50
+ {label}
51
+ </label>
52
+ )}
53
+
54
+ <div className="ui-select-container">
55
+ <select
56
+ ref={ref}
57
+ id={id}
58
+ disabled={disabled}
59
+ value={value}
60
+ defaultValue={defaultValue}
61
+ aria-invalid={hasError}
62
+ aria-describedby={helperText || errorMessage ? helperId : undefined}
63
+ className={selectClasses}
64
+ {...rest}
65
+ >
66
+ {placeholder && (
67
+ <option value="" disabled>
68
+ {placeholder}
69
+ </option>
70
+ )}
71
+
72
+ {options
73
+ ? options.map((opt) => (
74
+ <option key={opt.value} value={opt.value} disabled={opt.disabled}>
75
+ {opt.label}
76
+ </option>
77
+ ))
78
+ : children}
79
+ </select>
80
+
81
+ <span className="ui-select-icon" aria-hidden="true">
82
+ <svg
83
+ width="16"
84
+ height="16"
85
+ viewBox="0 0 24 24"
86
+ fill="none"
87
+ stroke="currentColor"
88
+ strokeWidth="2"
89
+ strokeLinecap="round"
90
+ strokeLinejoin="round"
91
+ >
92
+ <polyline points="6 9 12 15 18 9" />
93
+ </svg>
94
+ </span>
95
+ </div>
96
+
97
+ {(errorMessage || helperText) && (
98
+ <span
99
+ id={helperId}
100
+ className={cx('ui-select-helper', hasError && 'ui-select-helper--error')}
101
+ >
102
+ {errorMessage || helperText}
103
+ </span>
104
+ )}
105
+ </div>
106
+ );
107
+ });
108
+
109
+ Select.displayName = 'Select';
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+
3
+ export interface SelectOption {
4
+ label: string;
5
+ value: string | number;
6
+ disabled?: boolean;
7
+ }
8
+
9
+ export type SelectSize = 'sm' | 'md' | 'lg';
10
+
11
+ export interface SelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'size'> {
12
+ options?: SelectOption[];
13
+ placeholder?: string;
14
+ size?: SelectSize;
15
+ label?: React.ReactNode;
16
+ helperText?: React.ReactNode;
17
+ error?: string | boolean;
18
+ fullWidth?: boolean;
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Select';
2
+ export * from './Select.types';
@@ -0,0 +1,49 @@
1
+ .ui-text {
2
+ margin: 0;
3
+ padding: 0;
4
+ font-family: var(--ui-font-sans);
5
+ line-height: var(--ui-line-height-normal);
6
+ color: var(--ui-color-foreground);
7
+ transition: color var(--ui-transition-fast);
8
+ }
9
+
10
+ /* Sizes */
11
+ .ui-text--size-xs { font-size: var(--ui-font-size-xs); }
12
+ .ui-text--size-sm { font-size: var(--ui-font-size-sm); }
13
+ .ui-text--size-md { font-size: var(--ui-font-size-md); }
14
+ .ui-text--size-lg { font-size: var(--ui-font-size-lg); }
15
+ .ui-text--size-xl { font-size: var(--ui-font-size-xl); }
16
+ .ui-text--size-2xl { font-size: var(--ui-font-size-2xl); }
17
+
18
+ /* Weights */
19
+ .ui-text--weight-normal { font-weight: var(--ui-font-weight-normal); }
20
+ .ui-text--weight-medium { font-weight: var(--ui-font-weight-medium); }
21
+ .ui-text--weight-semibold { font-weight: var(--ui-font-weight-semibold); }
22
+ .ui-text--weight-bold { font-weight: var(--ui-font-weight-bold); }
23
+
24
+ /* Colors */
25
+ .ui-text--color-default { color: var(--ui-color-foreground); }
26
+ .ui-text--color-muted { color: var(--ui-color-foreground-muted); }
27
+ .ui-text--color-subtle { color: var(--ui-color-foreground-subtle); }
28
+ .ui-text--color-primary { color: var(--ui-color-primary); }
29
+ .ui-text--color-success { color: var(--ui-color-success); }
30
+ .ui-text--color-warning { color: var(--ui-color-warning); }
31
+ .ui-text--color-danger { color: var(--ui-color-danger); }
32
+ .ui-text--color-inherit { color: inherit; }
33
+
34
+ /* Alignments */
35
+ .ui-text--align-left { text-align: left; }
36
+ .ui-text--align-center { text-align: center; }
37
+ .ui-text--align-right { text-align: right; }
38
+ .ui-text--align-justify { text-align: justify; }
39
+
40
+ /* Modifiers */
41
+ .ui-text--truncate {
42
+ overflow: hidden;
43
+ text-overflow: ellipsis;
44
+ white-space: nowrap;
45
+ }
46
+
47
+ .ui-text--mono {
48
+ font-family: var(--ui-font-mono);
49
+ }
@@ -0,0 +1,46 @@
1
+ import React, { forwardRef } from 'react';
2
+ import type { TextProps } from './Text.types';
3
+ import { cx } from '../utils';
4
+ import './Text.css';
5
+
6
+ export const Text = forwardRef<HTMLElement, TextProps>(function Text(
7
+ {
8
+ as: Component = 'p',
9
+ size = 'md',
10
+ weight = 'normal',
11
+ color = 'default',
12
+ muted = false,
13
+ align,
14
+ truncate = false,
15
+ mono = false,
16
+ className,
17
+ children,
18
+ ...rest
19
+ },
20
+ ref
21
+ ) {
22
+ const resolvedColor = muted ? 'muted' : color;
23
+
24
+ const classes = cx(
25
+ 'ui-text',
26
+ `ui-text--size-${size}`,
27
+ `ui-text--weight-${weight}`,
28
+ `ui-text--color-${resolvedColor}`,
29
+ align && `ui-text--align-${align}`,
30
+ truncate && 'ui-text--truncate',
31
+ mono && 'ui-text--mono',
32
+ className
33
+ );
34
+
35
+ return React.createElement(
36
+ Component,
37
+ {
38
+ ref,
39
+ className: classes,
40
+ ...rest,
41
+ },
42
+ children
43
+ );
44
+ });
45
+
46
+ Text.displayName = 'Text';
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+
3
+ export type TextElement = 'p' | 'span' | 'div' | 'label' | 'strong' | 'em' | 'small' | 'code';
4
+ export type TextSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
5
+ export type TextWeight = 'normal' | 'medium' | 'semibold' | 'bold';
6
+ export type TextColor = 'default' | 'muted' | 'subtle' | 'primary' | 'success' | 'warning' | 'danger' | 'inherit';
7
+ export type TextAlign = 'left' | 'center' | 'right' | 'justify';
8
+
9
+ export interface TextProps extends React.HTMLAttributes<HTMLElement> {
10
+ as?: TextElement;
11
+ size?: TextSize;
12
+ weight?: TextWeight;
13
+ color?: TextColor;
14
+ muted?: boolean;
15
+ align?: TextAlign;
16
+ truncate?: boolean;
17
+ mono?: boolean;
18
+ children?: React.ReactNode;
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Text';
2
+ export * from './Text.types';
package/src/index.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Atelier UI — Modern, minimalist, accessible React + TypeScript UI framework
3
+ */
4
+
5
+ // Foundation
6
+ export * from './Text';
7
+ export * from './Heading';
8
+ export * from './Icon';
9
+ export * from './Divider';
10
+
11
+ // Actions
12
+ export * from './Button';
13
+ export * from './Link';
14
+
15
+ // Forms
16
+ export * from './Input';
17
+ export * from './Checkbox';
18
+ export * from './Select';
19
+
20
+ // Containers
21
+ export * from './Card';
22
+
23
+ // Layout
24
+ export * from './layout/Stack';
25
+ export * from './layout/Flex';
26
+ export * from './layout/Grid';
27
+ export * from './layout/Container';
28
+
29
+ // Theme
30
+ export * from './theme';
31
+
32
+ // Utils
33
+ export { cx, getSpaceValue } from './utils';
34
+ export type { SpaceToken, ClassValue } from './utils';
@@ -0,0 +1,16 @@
1
+ .ui-container {
2
+ width: 100%;
3
+ box-sizing: border-box;
4
+ }
5
+
6
+ .ui-container--centered {
7
+ margin-left: auto;
8
+ margin-right: auto;
9
+ }
10
+
11
+ /* Container max-widths */
12
+ .ui-container--size-sm { max-width: 640px; }
13
+ .ui-container--size-md { max-width: 768px; }
14
+ .ui-container--size-lg { max-width: 1024px; }
15
+ .ui-container--size-xl { max-width: 1280px; }
16
+ .ui-container--size-full { max-width: 100%; }
@@ -0,0 +1,51 @@
1
+ import React, { forwardRef } from 'react';
2
+ import type { ContainerProps } from './Container.types';
3
+ import { cx, getSpaceValue } from '../../utils';
4
+ import './Container.css';
5
+
6
+ export const Container = forwardRef<HTMLDivElement, ContainerProps>(function Container(
7
+ {
8
+ as: Component = 'div',
9
+ size = 'lg',
10
+ centered = true,
11
+ padding = 'md',
12
+ className,
13
+ style,
14
+ children,
15
+ ...rest
16
+ },
17
+ ref
18
+ ) {
19
+ const classes = cx(
20
+ 'ui-container',
21
+ `ui-container--size-${size}`,
22
+ centered && 'ui-container--centered',
23
+ className
24
+ );
25
+
26
+ let paddingValue: string | undefined;
27
+ if (typeof padding === 'boolean') {
28
+ paddingValue = padding ? 'var(--ui-space-md)' : '0';
29
+ } else if (padding) {
30
+ paddingValue = getSpaceValue(padding);
31
+ }
32
+
33
+ const combinedStyle: React.CSSProperties = {
34
+ paddingLeft: paddingValue,
35
+ paddingRight: paddingValue,
36
+ ...style,
37
+ };
38
+
39
+ return React.createElement(
40
+ Component,
41
+ {
42
+ ref,
43
+ className: classes,
44
+ style: combinedStyle,
45
+ ...rest,
46
+ },
47
+ children
48
+ );
49
+ });
50
+
51
+ Container.displayName = 'Container';
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import type { SpaceToken } from '../../utils';
3
+
4
+ export type ContainerSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
5
+
6
+ export interface ContainerProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ as?: React.ElementType;
8
+ size?: ContainerSize;
9
+ centered?: boolean;
10
+ padding?: SpaceToken | boolean;
11
+ children?: React.ReactNode;
12
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Container';
2
+ export * from './Container.types';
@@ -0,0 +1,34 @@
1
+ .ui-flex {
2
+ display: flex;
3
+ box-sizing: border-box;
4
+ }
5
+
6
+ .ui-flex--inline {
7
+ display: inline-flex;
8
+ }
9
+
10
+ /* Directions */
11
+ .ui-flex--direction-row { flex-direction: row; }
12
+ .ui-flex--direction-row-reverse { flex-direction: row-reverse; }
13
+ .ui-flex--direction-col { flex-direction: column; }
14
+ .ui-flex--direction-col-reverse { flex-direction: column-reverse; }
15
+
16
+ /* Align */
17
+ .ui-flex--align-start { align-items: flex-start; }
18
+ .ui-flex--align-center { align-items: center; }
19
+ .ui-flex--align-end { align-items: flex-end; }
20
+ .ui-flex--align-stretch { align-items: stretch; }
21
+ .ui-flex--align-baseline { align-items: baseline; }
22
+
23
+ /* Justify */
24
+ .ui-flex--justify-start { justify-content: flex-start; }
25
+ .ui-flex--justify-center { justify-content: center; }
26
+ .ui-flex--justify-end { justify-content: flex-end; }
27
+ .ui-flex--justify-between { justify-content: space-between; }
28
+ .ui-flex--justify-around { justify-content: space-around; }
29
+ .ui-flex--justify-evenly { justify-content: space-evenly; }
30
+
31
+ /* Wrap */
32
+ .ui-flex--wrap { flex-wrap: wrap; }
33
+ .ui-flex--wrap-nowrap { flex-wrap: nowrap; }
34
+ .ui-flex--wrap-reverse { flex-wrap: wrap-reverse; }
@@ -0,0 +1,56 @@
1
+ import React, { forwardRef } from 'react';
2
+ import type { FlexProps } from './Flex.types';
3
+ import { cx, getSpaceValue } from '../../utils';
4
+ import './Flex.css';
5
+
6
+ export const Flex = forwardRef<HTMLElement, FlexProps>(function Flex(
7
+ {
8
+ as: Component = 'div',
9
+ direction = 'row',
10
+ align,
11
+ justify,
12
+ gap,
13
+ wrap,
14
+ inline = false,
15
+ className,
16
+ style,
17
+ children,
18
+ ...rest
19
+ },
20
+ ref
21
+ ) {
22
+ const resolvedGap = getSpaceValue(gap);
23
+
24
+ const wrapClass =
25
+ typeof wrap === 'boolean'
26
+ ? wrap ? 'ui-flex--wrap' : 'ui-flex--wrap-nowrap'
27
+ : wrap ? `ui-flex--wrap-${wrap}` : undefined;
28
+
29
+ const classes = cx(
30
+ 'ui-flex',
31
+ inline && 'ui-flex--inline',
32
+ direction && `ui-flex--direction-${direction}`,
33
+ align && `ui-flex--align-${align}`,
34
+ justify && `ui-flex--justify-${justify}`,
35
+ wrapClass,
36
+ className
37
+ );
38
+
39
+ const combinedStyle: React.CSSProperties = {
40
+ gap: resolvedGap,
41
+ ...style,
42
+ };
43
+
44
+ return React.createElement(
45
+ Component,
46
+ {
47
+ ref,
48
+ className: classes,
49
+ style: combinedStyle,
50
+ ...rest,
51
+ },
52
+ children
53
+ );
54
+ });
55
+
56
+ Flex.displayName = 'Flex';
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import type { SpaceToken } from '../../utils';
3
+
4
+ export type FlexDirection = 'row' | 'row-reverse' | 'col' | 'col-reverse';
5
+ export type FlexAlign = 'start' | 'center' | 'end' | 'stretch' | 'baseline';
6
+ export type FlexJustify = 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
7
+ export type FlexWrap = boolean | 'wrap' | 'nowrap' | 'wrap-reverse';
8
+
9
+ export interface FlexProps extends React.HTMLAttributes<HTMLElement> {
10
+ as?: React.ElementType;
11
+ direction?: FlexDirection;
12
+ align?: FlexAlign;
13
+ justify?: FlexJustify;
14
+ gap?: SpaceToken;
15
+ wrap?: FlexWrap;
16
+ inline?: boolean;
17
+ children?: React.ReactNode;
18
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Flex';
2
+ export * from './Flex.types';