@attrus-ds/core 0.2.0 → 0.3.1

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 (80) hide show
  1. package/LICENSE +27 -27
  2. package/dist/assets/flags/Brazil-rounded.svg +15 -15
  3. package/dist/assets/flags/Brazil-square.svg +16 -16
  4. package/dist/assets/flags/Chile-rounded.svg +10 -10
  5. package/dist/assets/flags/Chile-square.svg +10 -10
  6. package/dist/assets/flags/Colombia-rounded.svg +9 -9
  7. package/dist/assets/flags/Colombia-square.svg +9 -9
  8. package/dist/assets/flags/EUA-rounded.svg +10 -10
  9. package/dist/assets/flags/EUA-square.svg +10 -10
  10. package/dist/assets/flags/EUR-rounded.svg +8 -8
  11. package/dist/assets/flags/EUR-square.svg +8 -8
  12. package/dist/assets/flags/Mexico-rounded.svg +18 -18
  13. package/dist/assets/flags/Mexico-square.svg +18 -18
  14. package/dist/assets/icon-attrus-dark.svg +2 -2
  15. package/dist/assets/icon-attrus-light.svg +2 -2
  16. package/dist/assets/logo-attrus-dark.svg +7 -7
  17. package/dist/assets/logo-attrus-light.svg +7 -7
  18. package/dist/css/components/buttons.css +301 -301
  19. package/dist/css/components/callout.css +195 -195
  20. package/dist/css/components/cards.css +272 -272
  21. package/dist/css/components/combobox.css +319 -319
  22. package/dist/css/components/empty-states.css +61 -61
  23. package/dist/css/components/inputs.css +975 -975
  24. package/dist/css/components/number-input.css +138 -138
  25. package/dist/css/components/overlays.css +374 -374
  26. package/dist/css/components/primitives.css +314 -314
  27. package/dist/css/components/sidebar.css +179 -179
  28. package/dist/css/components/smart-search.css +91 -91
  29. package/dist/css/components/status-pills.css +207 -207
  30. package/dist/css/components/stepper.css +309 -309
  31. package/dist/css/components/table.css +602 -602
  32. package/dist/css/components/tabs.css +319 -319
  33. package/dist/css/components/toast.css +243 -243
  34. package/dist/css/components/tooltip.css +231 -231
  35. package/dist/css/components/topbar.css +148 -148
  36. package/dist/css/tokens.css +666 -666
  37. package/dist/react/AppSidebar/AppSidebar.d.ts +50 -50
  38. package/dist/react/AppSidebar/AppSidebar.tsx +197 -197
  39. package/dist/react/AppTopbar/AppTopbar.d.ts +58 -58
  40. package/dist/react/AppTopbar/AppTopbar.tsx +203 -203
  41. package/dist/react/Atoms/Atoms.d.ts +149 -149
  42. package/dist/react/Atoms/Atoms.tsx +407 -407
  43. package/dist/react/Button/Button.d.ts +25 -25
  44. package/dist/react/Button/Button.tsx +83 -83
  45. package/dist/react/Callout/Callout.d.ts +29 -29
  46. package/dist/react/Callout/Callout.tsx +98 -98
  47. package/dist/react/Card/Card.d.ts +38 -38
  48. package/dist/react/Card/Card.tsx +93 -93
  49. package/dist/react/Combobox/Combobox.d.ts +52 -52
  50. package/dist/react/Combobox/Combobox.tsx +254 -254
  51. package/dist/react/DataTable/DataTable.d.ts +81 -81
  52. package/dist/react/DataTable/DataTable.tsx +308 -308
  53. package/dist/react/Drawer/Drawer.d.ts +35 -35
  54. package/dist/react/Drawer/Drawer.tsx +226 -226
  55. package/dist/react/Input/Input.d.ts +155 -155
  56. package/dist/react/Input/Input.tsx +478 -478
  57. package/dist/react/Modal/Modal.d.ts +27 -27
  58. package/dist/react/Modal/Modal.tsx +173 -173
  59. package/dist/react/NumberInput/NumberInput.d.ts +25 -25
  60. package/dist/react/NumberInput/NumberInput.tsx +117 -117
  61. package/dist/react/Pill/Pill.d.ts +31 -31
  62. package/dist/react/Pill/Pill.tsx +104 -104
  63. package/dist/react/SearchPill/SearchPill.d.ts +14 -14
  64. package/dist/react/SearchPill/SearchPill.tsx +40 -40
  65. package/dist/react/SmartSearch/SmartSearch.d.ts +65 -65
  66. package/dist/react/SmartSearch/SmartSearch.tsx +271 -271
  67. package/dist/react/Stepper/Stepper.d.ts +25 -25
  68. package/dist/react/Stepper/Stepper.tsx +94 -94
  69. package/dist/react/Tabs/Tabs.d.ts +32 -32
  70. package/dist/react/Tabs/Tabs.tsx +147 -147
  71. package/dist/react/Toast/Toast.d.ts +30 -30
  72. package/dist/react/Toast/Toast.tsx +94 -94
  73. package/dist/react/Tooltip/Tooltip.d.ts +23 -23
  74. package/dist/react/Tooltip/Tooltip.tsx +80 -80
  75. package/dist/react/ZeroState/ZeroState.d.ts +17 -17
  76. package/dist/react/ZeroState/ZeroState.tsx +45 -45
  77. package/dist/react/index.ts +35 -35
  78. package/dist/tokens/attrus-tokens.flat.json +1234 -1234
  79. package/dist/tokens/attrus-tokens.json +1089 -1089
  80. package/package.json +64 -64
@@ -1,27 +1,27 @@
1
- import * as React from 'react';
2
-
3
- export type ModalSize = 'sm' | 'md' | 'lg' | 'xl';
4
-
5
- export interface ModalProps {
6
- open: boolean;
7
- onClose?: () => void;
8
- size?: ModalSize;
9
- title?: React.ReactNode;
10
- subtitle?: React.ReactNode;
11
- /** Icon tile above the title (destructive confirmations). */
12
- icon?: React.ReactNode;
13
- /** Danger accent strip + icon tint (.modal-destructive). */
14
- destructive?: boolean;
15
- /** Footer row (.modal-footer) — canonical Buttons, right-aligned. */
16
- footer?: React.ReactNode;
17
- /** Left-side footer aux content (enables space-between). */
18
- footerAux?: React.ReactNode;
19
- /** Gallery/doc rendering: panel only (no scrim), overlay behaviours off
20
- (focus trap, scroll-lock, Esc). Position it inside a relative frame. */
21
- static?: boolean;
22
- children?: React.ReactNode;
23
- className?: string;
24
- }
25
-
26
- export declare const Modal: React.FC<ModalProps>;
27
- export default Modal;
1
+ import * as React from 'react';
2
+
3
+ export type ModalSize = 'sm' | 'md' | 'lg' | 'xl';
4
+
5
+ export interface ModalProps {
6
+ open: boolean;
7
+ onClose?: () => void;
8
+ size?: ModalSize;
9
+ title?: React.ReactNode;
10
+ subtitle?: React.ReactNode;
11
+ /** Icon tile above the title (destructive confirmations). */
12
+ icon?: React.ReactNode;
13
+ /** Danger accent strip + icon tint (.modal-destructive). */
14
+ destructive?: boolean;
15
+ /** Footer row (.modal-footer) — canonical Buttons, right-aligned. */
16
+ footer?: React.ReactNode;
17
+ /** Left-side footer aux content (enables space-between). */
18
+ footerAux?: React.ReactNode;
19
+ /** Gallery/doc rendering: panel only (no scrim), overlay behaviours off
20
+ (focus trap, scroll-lock, Esc). Position it inside a relative frame. */
21
+ static?: boolean;
22
+ children?: React.ReactNode;
23
+ className?: string;
24
+ }
25
+
26
+ export declare const Modal: React.FC<ModalProps>;
27
+ export default Modal;
@@ -1,173 +1,173 @@
1
- import * as React from 'react';
2
-
3
- /**
4
- * ATTRUS Modal — typed wrapper over the canonical `.modal` + `.scrim`
5
- * (preview/components/overlays.css). Centered dialog with header/body/
6
- * footer, sizes sm→xl and the destructive variant (top accent strip).
7
- *
8
- * Open/close: mount with open=true; an internal flag flips `.is-open`
9
- * a tick later via setTimeout (NOT requestAnimationFrame — rAF is
10
- * throttled in background tabs and the overlay would stay invisible).
11
- * Esc and scrim-click call onClose. Exit: on open=false the dialog stays
12
- * mounted for the exit transition (fade + slide, --duration-base = 200ms)
13
- * and only then unmounts — closing mirrors opening.
14
- */
15
-
16
- export type ModalSize = 'sm' | 'md' | 'lg' | 'xl';
17
-
18
- export interface ModalProps {
19
- open: boolean;
20
- onClose?: () => void;
21
- size?: ModalSize;
22
- title?: React.ReactNode;
23
- subtitle?: React.ReactNode;
24
- /** Icon tile above the title (destructive confirmations). */
25
- icon?: React.ReactNode;
26
- /** Danger accent strip + icon tint (.modal-destructive). */
27
- destructive?: boolean;
28
- /** Footer row (.modal-footer) — canonical Buttons, right-aligned. */
29
- footer?: React.ReactNode;
30
- /** Left-side footer aux content (enables space-between). */
31
- footerAux?: React.ReactNode;
32
- /** Gallery/doc rendering: panel only (no scrim), overlay behaviours off
33
- (focus trap, scroll-lock, Esc). Position it inside a relative frame. */
34
- static?: boolean;
35
- children?: React.ReactNode;
36
- className?: string;
37
- }
38
-
39
- const XGlyph: React.FC = () => (
40
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" aria-hidden="true">
41
- <line x1="18" y1="6" x2="6" y2="18" />
42
- <line x1="6" y1="6" x2="18" y2="18" />
43
- </svg>
44
- );
45
-
46
- export const Modal: React.FC<ModalProps> = ({
47
- open,
48
- onClose,
49
- size = 'md',
50
- title,
51
- subtitle,
52
- icon,
53
- destructive = false,
54
- footer,
55
- footerAux,
56
- static: isStatic = false,
57
- children,
58
- className,
59
- }) => {
60
- const [shown, setShown] = React.useState(false);
61
- const [mounted, setMounted] = React.useState(open);
62
- const panelRef = React.useRef<HTMLDivElement>(null);
63
-
64
- React.useEffect(() => {
65
- if (open) {
66
- setMounted(true);
67
- return undefined;
68
- }
69
- // Closing: drop .is-open → CSS exit transition plays → unmount after.
70
- setShown(false);
71
- const t = setTimeout(() => setMounted(false), 200);
72
- return () => clearTimeout(t);
73
- }, [open]);
74
-
75
- // Entry: commit the CLOSED state, force a layout flush, then flip
76
- // .is-open — the fade+slide entry plays deterministically (a bare
77
- // 10ms timer raced the first style calc and often skipped it).
78
- React.useEffect(() => {
79
- if (!open || !mounted || shown) return;
80
- void panelRef.current?.offsetHeight;
81
- setShown(true);
82
- }, [open, mounted, shown]);
83
-
84
- React.useEffect(() => {
85
- if (!open || isStatic) return undefined;
86
- const onKey = (e: KeyboardEvent) => {
87
- if (e.key === 'Escape' && onClose) onClose();
88
- };
89
- document.addEventListener('keydown', onKey);
90
- return () => document.removeEventListener('keydown', onKey);
91
- }, [open, onClose, isStatic]);
92
-
93
- // Body scroll-lock while open (doc a11y contract).
94
- React.useEffect(() => {
95
- if (!open || !mounted || isStatic) return undefined;
96
- const prev = document.body.style.overflow;
97
- document.body.style.overflow = 'hidden';
98
- return () => { document.body.style.overflow = prev; };
99
- }, [open, mounted, isStatic]);
100
-
101
- // Focus: into the dialog on open, restore the opener on close; Tab cycles.
102
- const prevFocus = React.useRef<HTMLElement | null>(null);
103
- React.useEffect(() => {
104
- if (isStatic) return;
105
- if (shown) {
106
- prevFocus.current = document.activeElement as HTMLElement | null;
107
- const first = panelRef.current?.querySelector<HTMLElement>(
108
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
109
- );
110
- (first || panelRef.current)?.focus?.();
111
- } else if (prevFocus.current) {
112
- prevFocus.current.focus?.();
113
- prevFocus.current = null;
114
- }
115
- }, [shown]);
116
-
117
- const onTrapKeyDown = (e: React.KeyboardEvent) => {
118
- if (e.key !== 'Tab' || !panelRef.current) return;
119
- const focusables = Array.from(
120
- panelRef.current.querySelectorAll<HTMLElement>(
121
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
122
- )
123
- ).filter((el) => !el.hasAttribute('disabled'));
124
- if (!focusables.length) return;
125
- const first = focusables[0];
126
- const last = focusables[focusables.length - 1];
127
- if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
128
- else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
129
- };
130
-
131
- if (!mounted) return null;
132
-
133
- const cls = [
134
- 'modal',
135
- `modal-${size}`,
136
- destructive ? 'modal-destructive' : '',
137
- shown ? 'is-open' : '',
138
- className || '',
139
- ]
140
- .filter(Boolean)
141
- .join(' ');
142
-
143
- return (
144
- <React.Fragment>
145
- {isStatic ? null : <div className={['scrim', shown ? 'is-open' : ''].filter(Boolean).join(' ')} onClick={onClose} />}
146
- <div ref={panelRef} className={cls} role="dialog" aria-modal="true" tabIndex={-1} onKeyDown={onTrapKeyDown}>
147
- {title != null || subtitle != null || onClose ? (
148
- <div className="modal-header">
149
- <div className="modal-header-text">
150
- {icon != null ? <span className="modal-icon">{icon}</span> : null}
151
- {title != null ? <div className="modal-title">{title}</div> : null}
152
- {subtitle != null ? <div className="modal-subtitle">{subtitle}</div> : null}
153
- </div>
154
- {onClose ? (
155
- <button type="button" className="modal-close" aria-label="Close" onClick={onClose}>
156
- <XGlyph />
157
- </button>
158
- ) : null}
159
- </div>
160
- ) : null}
161
- {children != null ? <div className="modal-body">{children}</div> : null}
162
- {footer != null ? (
163
- <div className={['modal-footer', footerAux != null ? 'modal-footer-spread' : ''].filter(Boolean).join(' ')}>
164
- {footerAux != null ? <div className="modal-footer-aux">{footerAux}</div> : null}
165
- {footer}
166
- </div>
167
- ) : null}
168
- </div>
169
- </React.Fragment>
170
- );
171
- };
172
-
173
- export default Modal;
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * ATTRUS Modal — typed wrapper over the canonical `.modal` + `.scrim`
5
+ * (preview/components/overlays.css). Centered dialog with header/body/
6
+ * footer, sizes sm→xl and the destructive variant (top accent strip).
7
+ *
8
+ * Open/close: mount with open=true; an internal flag flips `.is-open`
9
+ * a tick later via setTimeout (NOT requestAnimationFrame — rAF is
10
+ * throttled in background tabs and the overlay would stay invisible).
11
+ * Esc and scrim-click call onClose. Exit: on open=false the dialog stays
12
+ * mounted for the exit transition (fade + slide, --duration-base = 200ms)
13
+ * and only then unmounts — closing mirrors opening.
14
+ */
15
+
16
+ export type ModalSize = 'sm' | 'md' | 'lg' | 'xl';
17
+
18
+ export interface ModalProps {
19
+ open: boolean;
20
+ onClose?: () => void;
21
+ size?: ModalSize;
22
+ title?: React.ReactNode;
23
+ subtitle?: React.ReactNode;
24
+ /** Icon tile above the title (destructive confirmations). */
25
+ icon?: React.ReactNode;
26
+ /** Danger accent strip + icon tint (.modal-destructive). */
27
+ destructive?: boolean;
28
+ /** Footer row (.modal-footer) — canonical Buttons, right-aligned. */
29
+ footer?: React.ReactNode;
30
+ /** Left-side footer aux content (enables space-between). */
31
+ footerAux?: React.ReactNode;
32
+ /** Gallery/doc rendering: panel only (no scrim), overlay behaviours off
33
+ (focus trap, scroll-lock, Esc). Position it inside a relative frame. */
34
+ static?: boolean;
35
+ children?: React.ReactNode;
36
+ className?: string;
37
+ }
38
+
39
+ const XGlyph: React.FC = () => (
40
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" aria-hidden="true">
41
+ <line x1="18" y1="6" x2="6" y2="18" />
42
+ <line x1="6" y1="6" x2="18" y2="18" />
43
+ </svg>
44
+ );
45
+
46
+ export const Modal: React.FC<ModalProps> = ({
47
+ open,
48
+ onClose,
49
+ size = 'md',
50
+ title,
51
+ subtitle,
52
+ icon,
53
+ destructive = false,
54
+ footer,
55
+ footerAux,
56
+ static: isStatic = false,
57
+ children,
58
+ className,
59
+ }) => {
60
+ const [shown, setShown] = React.useState(false);
61
+ const [mounted, setMounted] = React.useState(open);
62
+ const panelRef = React.useRef<HTMLDivElement>(null);
63
+
64
+ React.useEffect(() => {
65
+ if (open) {
66
+ setMounted(true);
67
+ return undefined;
68
+ }
69
+ // Closing: drop .is-open → CSS exit transition plays → unmount after.
70
+ setShown(false);
71
+ const t = setTimeout(() => setMounted(false), 200);
72
+ return () => clearTimeout(t);
73
+ }, [open]);
74
+
75
+ // Entry: commit the CLOSED state, force a layout flush, then flip
76
+ // .is-open — the fade+slide entry plays deterministically (a bare
77
+ // 10ms timer raced the first style calc and often skipped it).
78
+ React.useEffect(() => {
79
+ if (!open || !mounted || shown) return;
80
+ void panelRef.current?.offsetHeight;
81
+ setShown(true);
82
+ }, [open, mounted, shown]);
83
+
84
+ React.useEffect(() => {
85
+ if (!open || isStatic) return undefined;
86
+ const onKey = (e: KeyboardEvent) => {
87
+ if (e.key === 'Escape' && onClose) onClose();
88
+ };
89
+ document.addEventListener('keydown', onKey);
90
+ return () => document.removeEventListener('keydown', onKey);
91
+ }, [open, onClose, isStatic]);
92
+
93
+ // Body scroll-lock while open (doc a11y contract).
94
+ React.useEffect(() => {
95
+ if (!open || !mounted || isStatic) return undefined;
96
+ const prev = document.body.style.overflow;
97
+ document.body.style.overflow = 'hidden';
98
+ return () => { document.body.style.overflow = prev; };
99
+ }, [open, mounted, isStatic]);
100
+
101
+ // Focus: into the dialog on open, restore the opener on close; Tab cycles.
102
+ const prevFocus = React.useRef<HTMLElement | null>(null);
103
+ React.useEffect(() => {
104
+ if (isStatic) return;
105
+ if (shown) {
106
+ prevFocus.current = document.activeElement as HTMLElement | null;
107
+ const first = panelRef.current?.querySelector<HTMLElement>(
108
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
109
+ );
110
+ (first || panelRef.current)?.focus?.();
111
+ } else if (prevFocus.current) {
112
+ prevFocus.current.focus?.();
113
+ prevFocus.current = null;
114
+ }
115
+ }, [shown]);
116
+
117
+ const onTrapKeyDown = (e: React.KeyboardEvent) => {
118
+ if (e.key !== 'Tab' || !panelRef.current) return;
119
+ const focusables = Array.from(
120
+ panelRef.current.querySelectorAll<HTMLElement>(
121
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
122
+ )
123
+ ).filter((el) => !el.hasAttribute('disabled'));
124
+ if (!focusables.length) return;
125
+ const first = focusables[0];
126
+ const last = focusables[focusables.length - 1];
127
+ if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
128
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
129
+ };
130
+
131
+ if (!mounted) return null;
132
+
133
+ const cls = [
134
+ 'modal',
135
+ `modal-${size}`,
136
+ destructive ? 'modal-destructive' : '',
137
+ shown ? 'is-open' : '',
138
+ className || '',
139
+ ]
140
+ .filter(Boolean)
141
+ .join(' ');
142
+
143
+ return (
144
+ <React.Fragment>
145
+ {isStatic ? null : <div className={['scrim', shown ? 'is-open' : ''].filter(Boolean).join(' ')} onClick={onClose} />}
146
+ <div ref={panelRef} className={cls} role="dialog" aria-modal="true" tabIndex={-1} onKeyDown={onTrapKeyDown}>
147
+ {title != null || subtitle != null || onClose ? (
148
+ <div className="modal-header">
149
+ <div className="modal-header-text">
150
+ {icon != null ? <span className="modal-icon">{icon}</span> : null}
151
+ {title != null ? <div className="modal-title">{title}</div> : null}
152
+ {subtitle != null ? <div className="modal-subtitle">{subtitle}</div> : null}
153
+ </div>
154
+ {onClose ? (
155
+ <button type="button" className="modal-close" aria-label="Close" onClick={onClose}>
156
+ <XGlyph />
157
+ </button>
158
+ ) : null}
159
+ </div>
160
+ ) : null}
161
+ {children != null ? <div className="modal-body">{children}</div> : null}
162
+ {footer != null ? (
163
+ <div className={['modal-footer', footerAux != null ? 'modal-footer-spread' : ''].filter(Boolean).join(' ')}>
164
+ {footerAux != null ? <div className="modal-footer-aux">{footerAux}</div> : null}
165
+ {footer}
166
+ </div>
167
+ ) : null}
168
+ </div>
169
+ </React.Fragment>
170
+ );
171
+ };
172
+
173
+ export default Modal;
@@ -1,25 +1,25 @@
1
- import * as React from 'react';
2
-
3
- export type NumberInputSize = 'sm' | 'md' | 'lg';
4
-
5
- export interface NumberInputProps {
6
- value: number;
7
- onChange?: (value: number) => void;
8
- min?: number;
9
- max?: number;
10
- step?: number;
11
- size?: NumberInputSize;
12
- /** Static cell before the field (e.g. "$"). */
13
- prefix?: React.ReactNode;
14
- /** Static cell after the field (e.g. "%", "days"). */
15
- suffix?: React.ReactNode;
16
- disabled?: boolean;
17
- invalid?: boolean;
18
- 'aria-label'?: string;
19
- id?: string;
20
- className?: string;
21
- style?: React.CSSProperties;
22
- }
23
-
24
- export declare const NumberInput: React.FC<NumberInputProps>;
25
- export default NumberInput;
1
+ import * as React from 'react';
2
+
3
+ export type NumberInputSize = 'sm' | 'md' | 'lg';
4
+
5
+ export interface NumberInputProps {
6
+ value: number;
7
+ onChange?: (value: number) => void;
8
+ min?: number;
9
+ max?: number;
10
+ step?: number;
11
+ size?: NumberInputSize;
12
+ /** Static cell before the field (e.g. "$"). */
13
+ prefix?: React.ReactNode;
14
+ /** Static cell after the field (e.g. "%", "days"). */
15
+ suffix?: React.ReactNode;
16
+ disabled?: boolean;
17
+ invalid?: boolean;
18
+ 'aria-label'?: string;
19
+ id?: string;
20
+ className?: string;
21
+ style?: React.CSSProperties;
22
+ }
23
+
24
+ export declare const NumberInput: React.FC<NumberInputProps>;
25
+ export default NumberInput;