@attrus-ds/core 0.1.1 → 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 (82) hide show
  1. package/LICENSE +27 -27
  2. package/README.md +63 -9
  3. package/dist/assets/flags/Brazil-rounded.svg +15 -15
  4. package/dist/assets/flags/Brazil-square.svg +16 -16
  5. package/dist/assets/flags/Chile-rounded.svg +10 -10
  6. package/dist/assets/flags/Chile-square.svg +10 -10
  7. package/dist/assets/flags/Colombia-rounded.svg +9 -9
  8. package/dist/assets/flags/Colombia-square.svg +9 -9
  9. package/dist/assets/flags/EUA-rounded.svg +10 -10
  10. package/dist/assets/flags/EUA-square.svg +10 -10
  11. package/dist/assets/flags/EUR-rounded.svg +8 -8
  12. package/dist/assets/flags/EUR-square.svg +8 -8
  13. package/dist/assets/flags/Mexico-rounded.svg +18 -18
  14. package/dist/assets/flags/Mexico-square.svg +18 -18
  15. package/dist/assets/icon-attrus-dark.svg +2 -2
  16. package/dist/assets/icon-attrus-light.svg +2 -2
  17. package/dist/assets/logo-attrus-dark.svg +7 -7
  18. package/dist/assets/logo-attrus-light.svg +7 -7
  19. package/dist/css/components/buttons.css +301 -301
  20. package/dist/css/components/callout.css +195 -195
  21. package/dist/css/components/cards.css +272 -272
  22. package/dist/css/components/combobox.css +319 -303
  23. package/dist/css/components/empty-states.css +61 -61
  24. package/dist/css/components/inputs.css +975 -1027
  25. package/dist/css/components/number-input.css +138 -138
  26. package/dist/css/components/overlays.css +374 -405
  27. package/dist/css/components/primitives.css +314 -305
  28. package/dist/css/components/sidebar.css +179 -179
  29. package/dist/css/components/smart-search.css +91 -0
  30. package/dist/css/components/status-pills.css +207 -207
  31. package/dist/css/components/stepper.css +309 -309
  32. package/dist/css/components/table.css +602 -600
  33. package/dist/css/components/tabs.css +319 -319
  34. package/dist/css/components/toast.css +243 -239
  35. package/dist/css/components/tooltip.css +231 -226
  36. package/dist/css/components/topbar.css +148 -148
  37. package/dist/css/index.css +1 -0
  38. package/dist/css/tokens.css +666 -666
  39. package/dist/react/AppSidebar/AppSidebar.d.ts +50 -0
  40. package/dist/react/AppSidebar/AppSidebar.tsx +197 -0
  41. package/dist/react/AppTopbar/AppTopbar.d.ts +58 -0
  42. package/dist/react/AppTopbar/AppTopbar.tsx +203 -0
  43. package/dist/react/Atoms/Atoms.d.ts +149 -0
  44. package/dist/react/Atoms/Atoms.tsx +407 -0
  45. package/dist/react/Button/Button.d.ts +25 -0
  46. package/dist/react/Button/Button.tsx +83 -0
  47. package/dist/react/Callout/Callout.d.ts +29 -0
  48. package/dist/react/Callout/Callout.tsx +98 -0
  49. package/dist/react/Card/Card.d.ts +38 -0
  50. package/dist/react/Card/Card.tsx +93 -0
  51. package/dist/react/Combobox/Combobox.d.ts +52 -0
  52. package/dist/react/Combobox/Combobox.tsx +254 -0
  53. package/dist/react/DataTable/DataTable.d.ts +81 -0
  54. package/dist/react/DataTable/DataTable.tsx +308 -0
  55. package/dist/react/Drawer/Drawer.d.ts +35 -0
  56. package/dist/react/Drawer/Drawer.tsx +226 -0
  57. package/dist/react/Input/Input.d.ts +155 -0
  58. package/dist/react/Input/Input.tsx +478 -0
  59. package/dist/react/Modal/Modal.d.ts +27 -0
  60. package/dist/react/Modal/Modal.tsx +173 -0
  61. package/dist/react/NumberInput/NumberInput.d.ts +25 -0
  62. package/dist/react/NumberInput/NumberInput.tsx +117 -0
  63. package/dist/react/Pill/Pill.d.ts +31 -0
  64. package/dist/react/Pill/Pill.tsx +104 -0
  65. package/dist/react/SearchPill/SearchPill.d.ts +14 -0
  66. package/dist/react/SearchPill/SearchPill.tsx +40 -0
  67. package/dist/react/SmartSearch/SmartSearch.d.ts +65 -0
  68. package/dist/react/SmartSearch/SmartSearch.tsx +271 -0
  69. package/dist/react/Stepper/Stepper.d.ts +25 -0
  70. package/dist/react/Stepper/Stepper.tsx +94 -0
  71. package/dist/react/Tabs/Tabs.d.ts +32 -0
  72. package/dist/react/Tabs/Tabs.tsx +147 -0
  73. package/dist/react/Toast/Toast.d.ts +30 -0
  74. package/dist/react/Toast/Toast.tsx +94 -0
  75. package/dist/react/Tooltip/Tooltip.d.ts +23 -0
  76. package/dist/react/Tooltip/Tooltip.tsx +80 -0
  77. package/dist/react/ZeroState/ZeroState.d.ts +17 -0
  78. package/dist/react/ZeroState/ZeroState.tsx +45 -0
  79. package/dist/react/index.ts +35 -0
  80. package/dist/tokens/attrus-tokens.flat.json +1234 -1234
  81. package/dist/tokens/attrus-tokens.json +1089 -1089
  82. package/package.json +64 -54
@@ -1,8 +1,8 @@
1
- <svg width="1003" height="130" viewBox="0 0 1003 130" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M163.142 19.1138H221.901V129.352H246.304V19.1138H303.218V0H163.142V19.1138Z" fill="#000033"></path>
3
- <path d="M324.487 19.1138H383.22V129.352H407.649V19.1138H466.382V0H324.487V19.1138Z" fill="#000033"></path>
4
- <path d="M597.946 0H488.134V129.352H512.563V71.6632H580.687L610.977 129.352H637.065L606.589 71.2885C629.546 69.0933 638.537 57.1539 638.537 40.4227V31.2405C638.537 12.6889 627.326 0 597.946 0ZM614.108 40.6101C614.108 48.3198 608.97 52.5495 597.946 52.5495H512.536V19.1138H597.946C608.97 19.1138 614.108 23.3434 614.108 31.0531V40.6101Z" fill="#000033"></path>
5
- <path d="M798.257 98.2993C798.257 106.009 793.12 110.239 782.096 110.239H701.85C690.826 110.239 685.689 106.009 685.689 98.2993V0H661.26V98.1119C661.26 116.664 672.47 129.352 701.85 129.352H787.26C816.64 129.352 822.713 116.664 822.713 98.1119V0H798.284V98.2993H798.257Z" fill="#000033"></path>
6
- <path d="M961.636 50.8897H885.592C874.567 50.8897 869.43 46.66 869.43 38.9503V31.0532C869.43 23.3434 874.567 19.1137 885.592 19.1137H995.057V0H885.592C856.211 0 845 12.689 845 31.2406V38.7629C845 57.3145 856.211 70.0034 885.592 70.0034H961.636C972.66 70.0034 977.798 74.2331 977.798 81.9428V98.2993C977.798 106.009 972.66 110.239 961.636 110.239H849.255V129.353H961.636C991.015 129.353 1002.23 116.663 1002.23 98.112V82.1303C1002.23 63.5787 991.015 50.8897 961.636 50.8897Z" fill="#000033"></path>
7
- <path d="M108.233 6.74605C105.184 2.677 100.34 0 94.8819 0C89.4234 0 84.4464 2.73056 81.3961 6.8799L80.6203 8.03098L0 129.352H27.1854L90.0923 32.6594L94.9622 25.1637L99.7248 32.6325L161.615 129.352H189.176L109.304 8.37901L108.233 6.74605Z" fill="#000033"></path>
1
+ <svg width="1003" height="130" viewBox="0 0 1003 130" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M163.142 19.1138H221.901V129.352H246.304V19.1138H303.218V0H163.142V19.1138Z" fill="#000033"></path>
3
+ <path d="M324.487 19.1138H383.22V129.352H407.649V19.1138H466.382V0H324.487V19.1138Z" fill="#000033"></path>
4
+ <path d="M597.946 0H488.134V129.352H512.563V71.6632H580.687L610.977 129.352H637.065L606.589 71.2885C629.546 69.0933 638.537 57.1539 638.537 40.4227V31.2405C638.537 12.6889 627.326 0 597.946 0ZM614.108 40.6101C614.108 48.3198 608.97 52.5495 597.946 52.5495H512.536V19.1138H597.946C608.97 19.1138 614.108 23.3434 614.108 31.0531V40.6101Z" fill="#000033"></path>
5
+ <path d="M798.257 98.2993C798.257 106.009 793.12 110.239 782.096 110.239H701.85C690.826 110.239 685.689 106.009 685.689 98.2993V0H661.26V98.1119C661.26 116.664 672.47 129.352 701.85 129.352H787.26C816.64 129.352 822.713 116.664 822.713 98.1119V0H798.284V98.2993H798.257Z" fill="#000033"></path>
6
+ <path d="M961.636 50.8897H885.592C874.567 50.8897 869.43 46.66 869.43 38.9503V31.0532C869.43 23.3434 874.567 19.1137 885.592 19.1137H995.057V0H885.592C856.211 0 845 12.689 845 31.2406V38.7629C845 57.3145 856.211 70.0034 885.592 70.0034H961.636C972.66 70.0034 977.798 74.2331 977.798 81.9428V98.2993C977.798 106.009 972.66 110.239 961.636 110.239H849.255V129.353H961.636C991.015 129.353 1002.23 116.663 1002.23 98.112V82.1303C1002.23 63.5787 991.015 50.8897 961.636 50.8897Z" fill="#000033"></path>
7
+ <path d="M108.233 6.74605C105.184 2.677 100.34 0 94.8819 0C89.4234 0 84.4464 2.73056 81.3961 6.8799L80.6203 8.03098L0 129.352H27.1854L90.0923 32.6594L94.9622 25.1637L99.7248 32.6325L161.615 129.352H189.176L109.304 8.37901L108.233 6.74605Z" fill="#000033"></path>
8
8
  </svg>
@@ -1,8 +1,8 @@
1
- <svg width="1005" height="130" viewBox="0 0 1005 130" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M163.142 19.1138H221.901V129.352H246.304V19.1138H303.218V0H163.142V19.1138Z" fill="white"></path>
3
- <path d="M324.487 19.1138H383.22V129.352H407.649V19.1138H466.382V0H324.487V19.1138Z" fill="white"></path>
4
- <path d="M597.946 0H488.134V129.352H512.563V71.6632H580.687L610.977 129.352H637.065L606.589 71.2885C629.546 69.0933 638.537 57.1539 638.537 40.4227V31.2405C638.537 12.6889 627.326 0 597.946 0ZM614.108 40.6101C614.108 48.3198 608.97 52.5495 597.946 52.5495H512.536V19.1138H597.946C608.97 19.1138 614.108 23.3434 614.108 31.0531V40.6101Z" fill="white"></path>
5
- <path d="M798.257 98.2993C798.257 106.009 793.12 110.239 782.096 110.239H701.85C690.826 110.239 685.689 106.009 685.689 98.2993V0H661.26V98.1119C661.26 116.664 672.47 129.352 701.85 129.352H787.26C816.64 129.352 822.713 116.664 822.713 98.1119V0H798.284V98.2993H798.257Z" fill="white"></path>
6
- <path d="M963.641 50.9135H887.597C876.572 50.9135 871.434 46.6838 871.434 38.9741V31.077C871.434 23.3672 876.572 19.1375 887.597 19.1375H997.061V0.0238037H887.597C858.216 0.0238037 847.005 12.7128 847.005 31.2644V38.7867C847.005 57.3383 858.216 70.0272 887.597 70.0272H963.641C974.665 70.0272 979.803 74.2569 979.803 81.9666V98.3231C979.803 106.033 974.665 110.262 963.641 110.262H851.259V129.376H963.641C993.02 129.376 1004.23 116.687 1004.23 98.1358V82.1541C1004.23 63.6025 993.02 50.9135 963.641 50.9135Z" fill="white"></path>
7
- <path d="M108.233 6.74605C105.184 2.677 100.34 0 94.8819 0C89.4234 0 84.4464 2.73056 81.3961 6.8799L80.6203 8.03098L0 129.352H27.1854L90.0923 32.6594L94.9622 25.1637L99.7248 32.6325L161.615 129.352H189.176L109.304 8.37901L108.233 6.74605Z" fill="white"></path>
1
+ <svg width="1005" height="130" viewBox="0 0 1005 130" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M163.142 19.1138H221.901V129.352H246.304V19.1138H303.218V0H163.142V19.1138Z" fill="white"></path>
3
+ <path d="M324.487 19.1138H383.22V129.352H407.649V19.1138H466.382V0H324.487V19.1138Z" fill="white"></path>
4
+ <path d="M597.946 0H488.134V129.352H512.563V71.6632H580.687L610.977 129.352H637.065L606.589 71.2885C629.546 69.0933 638.537 57.1539 638.537 40.4227V31.2405C638.537 12.6889 627.326 0 597.946 0ZM614.108 40.6101C614.108 48.3198 608.97 52.5495 597.946 52.5495H512.536V19.1138H597.946C608.97 19.1138 614.108 23.3434 614.108 31.0531V40.6101Z" fill="white"></path>
5
+ <path d="M798.257 98.2993C798.257 106.009 793.12 110.239 782.096 110.239H701.85C690.826 110.239 685.689 106.009 685.689 98.2993V0H661.26V98.1119C661.26 116.664 672.47 129.352 701.85 129.352H787.26C816.64 129.352 822.713 116.664 822.713 98.1119V0H798.284V98.2993H798.257Z" fill="white"></path>
6
+ <path d="M963.641 50.9135H887.597C876.572 50.9135 871.434 46.6838 871.434 38.9741V31.077C871.434 23.3672 876.572 19.1375 887.597 19.1375H997.061V0.0238037H887.597C858.216 0.0238037 847.005 12.7128 847.005 31.2644V38.7867C847.005 57.3383 858.216 70.0272 887.597 70.0272H963.641C974.665 70.0272 979.803 74.2569 979.803 81.9666V98.3231C979.803 106.033 974.665 110.262 963.641 110.262H851.259V129.376H963.641C993.02 129.376 1004.23 116.687 1004.23 98.1358V82.1541C1004.23 63.6025 993.02 50.9135 963.641 50.9135Z" fill="white"></path>
7
+ <path d="M108.233 6.74605C105.184 2.677 100.34 0 94.8819 0C89.4234 0 84.4464 2.73056 81.3961 6.8799L80.6203 8.03098L0 129.352H27.1854L90.0923 32.6594L94.9622 25.1637L99.7248 32.6325L161.615 129.352H189.176L109.304 8.37901L108.233 6.74605Z" fill="white"></path>
8
8
  </svg>
@@ -1,301 +1,301 @@
1
- /* ============================================================
2
- BUTTONS — ATTRUS Design System
3
- Component CSS — pure tokens, no hardcoded values.
4
-
5
- Anatomy:
6
- [leading icon?] [label] [trailing icon?]
7
-
8
- Variants (semantic):
9
- .btn-primary | brand fill — main action
10
- .btn-secondary | neutral fill + hairline border — alternative actions
11
- .btn-tertiary | soft neutral fill (borderless) — quiet affirmative
12
- .btn-ghost | no chrome — toolbar / cancel / low priority
13
- .btn-link | inline text-link styling
14
- .btn-danger | destructive solid — irreversible
15
- .btn-danger-outline | destructive secondary — contextual remove
16
- .btn-success | strong positive — approve / confirm
17
- .btn-inverse | white pill on dark/brand surfaces
18
-
19
- Modifier:
20
- .btn-icon | icon-only (square hit-area)
21
-
22
- Sizes:
23
- .btn-xs (24h) .btn-sm (32h) default (40h) .btn-lg (48h)
24
-
25
- States: default | :hover | :active | :focus-visible | [disabled] | .is-loading
26
- ============================================================ */
27
-
28
-
29
- /* ----------------------------------------------------------------
30
- 1. BASE
31
- ---------------------------------------------------------------- */
32
- .btn {
33
- /* Layout */
34
- display: inline-flex;
35
- align-items: center;
36
- justify-content: center;
37
- gap: var(--space-2);
38
- white-space: nowrap;
39
- flex-shrink: 0;
40
-
41
- /* Box — default size = md (40px tall) */
42
- height: 40px;
43
- padding: 0 var(--space-5);
44
- border: 1px solid transparent;
45
- border-radius: var(--radius-pill);
46
-
47
- /* Type */
48
- font-family: var(--font-sans);
49
- font-size: var(--fs-md);
50
- font-weight: var(--w-semibold);
51
- line-height: 1;
52
- letter-spacing: var(--ls-body);
53
-
54
- /* Behavior */
55
- cursor: pointer;
56
- user-select: none;
57
- -webkit-tap-highlight-color: transparent;
58
- background: transparent;
59
- color: inherit;
60
-
61
- /* Motion */
62
- transition:
63
- background var(--transition-fast),
64
- border-color var(--transition-fast),
65
- color var(--transition-fast),
66
- box-shadow var(--transition-fast),
67
- transform var(--transition-fast);
68
- }
69
-
70
- .btn:active { transform: translateY(0.5px); }
71
-
72
- .btn:focus { outline: none; }
73
- .btn:focus-visible { box-shadow: var(--shadow-focus); }
74
-
75
- .btn[disabled],
76
- .btn.is-disabled {
77
- cursor: not-allowed;
78
- opacity: 0.5;
79
- pointer-events: none;
80
- }
81
-
82
- /* Icon sizing inside buttons (Lucide replaces <i> with <svg>) */
83
- .btn > i, .btn > svg,
84
- .btn > svg {
85
- width: var(--icon-sm);
86
- height: var(--icon-sm);
87
- stroke-width: var(--icon-stroke);
88
- flex-shrink: 0;
89
- }
90
-
91
-
92
- /* ----------------------------------------------------------------
93
- 2. SIZES
94
- ---------------------------------------------------------------- */
95
- .btn-xs {
96
- height: 24px;
97
- padding: 0 var(--space-3);
98
- font-size: var(--fs-sm);
99
- gap: var(--space-1);
100
- }
101
- .btn-xs > i, .btn-xs > svg, .btn-xs > svg { width: var(--icon-xs); height: var(--icon-xs); }
102
-
103
- .btn-sm {
104
- height: 32px;
105
- padding: 0 var(--space-4);
106
- font-size: var(--fs-md);
107
- }
108
- .btn-sm > i, .btn-sm > svg, .btn-sm > svg { width: var(--icon-xs); height: var(--icon-xs); }
109
-
110
- /* default = md (40px) — defined in .btn */
111
-
112
- .btn-lg {
113
- height: 48px;
114
- padding: 0 var(--space-6);
115
- font-size: var(--fs-base);
116
- gap: var(--space-3);
117
- }
118
- .btn-lg > i, .btn-lg > svg, .btn-lg > svg { width: var(--icon-md); height: var(--icon-md); }
119
-
120
-
121
- /* ----------------------------------------------------------------
122
- 3. ICON-ONLY MODIFIER
123
- ---------------------------------------------------------------- */
124
- .btn-icon { padding: 0; aspect-ratio: 1 / 1; }
125
- .btn-icon.btn-xs { width: 24px; }
126
- .btn-icon.btn-sm { width: 32px; }
127
- .btn-icon { width: 40px; }
128
- .btn-icon.btn-lg { width: 48px; }
129
-
130
-
131
- /* ----------------------------------------------------------------
132
- 4. VARIANTS
133
- ---------------------------------------------------------------- */
134
-
135
- /* PRIMARY — brand-blue solid */
136
- .btn-primary {
137
- background: var(--color-brand-secondary-500);
138
- color: var(--color-foreground-on-solid);
139
- }
140
- .btn-primary:hover { background: var(--color-brand-secondary-600); }
141
- .btn-primary:active { background: var(--color-brand-secondary-700); }
142
- .btn-primary:focus-visible { box-shadow: var(--shadow-focus); }
143
-
144
-
145
- /* SECONDARY — soft neutral fill + hairline border */
146
- .btn-secondary {
147
- background: var(--color-background-subtle);
148
- color: var(--color-foreground-default);
149
- border-color: var(--color-border-default);
150
- }
151
- .btn-secondary:hover { background: var(--color-background-hover); border-color: var(--color-border-strong); }
152
- .btn-secondary:active { background: var(--color-background-press); }
153
- .btn-secondary:focus-visible { box-shadow: var(--shadow-focus); }
154
-
155
-
156
- /* TERTIARY — soft neutral fill, borderless (quieter than secondary) */
157
- .btn-tertiary {
158
- background: color-mix(in srgb, var(--color-foreground-default) 7%, transparent);
159
- color: var(--color-foreground-default);
160
- border-color: transparent;
161
- }
162
- .btn-tertiary:hover { background: color-mix(in srgb, var(--color-foreground-default) 12%, transparent); }
163
- .btn-tertiary:active { background: color-mix(in srgb, var(--color-foreground-default) 16%, transparent); }
164
- .btn-tertiary:focus-visible { box-shadow: var(--shadow-focus); }
165
-
166
-
167
- /* GHOST — no chrome until hover */
168
- .btn-ghost {
169
- background: transparent;
170
- color: var(--color-foreground-secondary);
171
- }
172
- .btn-ghost:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
173
- .btn-ghost:active { background: var(--color-background-press); }
174
- .btn-ghost:focus-visible { box-shadow: var(--shadow-focus); }
175
-
176
-
177
- /* LINK — looks like inline text link */
178
- .btn-link {
179
- background: transparent;
180
- color: var(--color-foreground-highlight);
181
- height: auto;
182
- padding: 0;
183
- border: 0;
184
- border-radius: var(--radius-2xs);
185
- text-decoration: none;
186
- }
187
- .btn-link:hover { color: var(--color-brand-secondary-700); text-decoration: underline; text-underline-offset: 2px; }
188
- .btn-link:active { color: var(--color-brand-secondary-800); }
189
- .btn-link:focus-visible { box-shadow: var(--shadow-focus); }
190
-
191
-
192
- /* DANGER — destructive solid */
193
- .btn-danger {
194
- background: var(--color-danger-500);
195
- color: var(--color-foreground-on-solid);
196
- }
197
- .btn-danger:hover { background: var(--color-danger-700); }
198
- .btn-danger:active { background: var(--color-danger-900); }
199
- .btn-danger:focus-visible { box-shadow: var(--shadow-focus-danger); }
200
-
201
-
202
- /* DANGER OUTLINE — destructive contextual */
203
- .btn-danger-outline {
204
- background: var(--color-background-surface);
205
- color: var(--color-danger-500);
206
- border-color: var(--color-feedback-danger-border);
207
- }
208
- .btn-danger-outline:hover { background: var(--color-feedback-danger-background); border-color: var(--color-danger-500); }
209
- .btn-danger-outline:active { background: color-mix(in srgb, var(--color-feedback-danger-background) 60%, var(--color-danger-100)); }
210
- .btn-danger-outline:focus-visible { box-shadow: var(--shadow-focus-danger); }
211
-
212
-
213
- /* SUCCESS — strong positive (rare) */
214
- .btn-success {
215
- background: var(--color-success-500);
216
- color: var(--color-foreground-on-solid);
217
- }
218
- .btn-success:hover { background: var(--color-success-700); }
219
- .btn-success:active { background: var(--color-success-900); }
220
- .btn-success:focus-visible { box-shadow: var(--shadow-focus-success); }
221
-
222
-
223
- /* INVERSE — white pill on dark/brand surfaces */
224
- .btn-inverse {
225
- background: var(--color-pure-white);
226
- color: var(--color-brand-secondary-700);
227
- }
228
- .btn-inverse:hover { background: var(--color-neutral-100); color: var(--color-brand-secondary-800); }
229
- .btn-inverse:active { background: var(--color-neutral-150); }
230
- .btn-inverse:focus-visible { box-shadow: var(--shadow-focus-sky); }
231
-
232
-
233
- /* ----------------------------------------------------------------
234
- 5. ON-INVERSE GHOSTS — for use inside .inverse / dark surfaces
235
- Apply BOTH .btn-ghost AND .on-inverse
236
- ---------------------------------------------------------------- */
237
- .btn-ghost.on-inverse {
238
- color: var(--color-foreground-inverse-secondary);
239
- }
240
- .btn-ghost.on-inverse:hover { background: var(--color-background-inverse-hover); color: var(--color-foreground-inverse-default); }
241
- .btn-ghost.on-inverse:active { background: var(--color-background-inverse-press); }
242
-
243
- /* Outline ghost on inverse — pairs with .btn-secondary semantics */
244
- .btn-secondary.on-inverse {
245
- background: transparent;
246
- color: var(--color-foreground-inverse-default);
247
- border-color: var(--color-border-inverse-strong);
248
- }
249
- .btn-secondary.on-inverse:hover { background: var(--color-background-inverse-hover); border-color: var(--color-foreground-inverse-secondary); }
250
- .btn-secondary.on-inverse:active { background: var(--color-background-inverse-press); }
251
-
252
-
253
- /* ----------------------------------------------------------------
254
- 6. LOADING STATE
255
- Add .is-loading; keeps current width but hides label/icons,
256
- shows a centered spinner.
257
- ---------------------------------------------------------------- */
258
- .btn.is-loading {
259
- position: relative;
260
- color: transparent !important;
261
- pointer-events: none;
262
- }
263
- .btn.is-loading > * { visibility: hidden; }
264
- .btn.is-loading::after {
265
- content: "";
266
- position: absolute;
267
- top: 50%; left: 50%;
268
- width: var(--icon-sm);
269
- height: var(--icon-sm);
270
- margin: calc(var(--icon-sm) / -2) 0 0 calc(var(--icon-sm) / -2);
271
- border: 2px solid currentColor;
272
- border-top-color: transparent;
273
- border-radius: var(--radius-pill);
274
- animation: btn-spin var(--duration-slower) linear infinite;
275
- /* Re-introduce currentColor where the variant set it; ::after inherits the parent's color BEFORE the !important override */
276
- color: var(--color-foreground-default);
277
- }
278
- .btn-primary.is-loading::after,
279
- .btn-danger.is-loading::after,
280
- .btn-success.is-loading::after { color: var(--color-pure-white); }
281
- .btn-tertiary.is-loading::after { color: var(--color-foreground-default); }
282
-
283
- @keyframes btn-spin {
284
- to { transform: rotate(360deg); }
285
- }
286
-
287
-
288
- /* ----------------------------------------------------------------
289
- 7. BUTTON GROUP — segmented / toolbar
290
- ---------------------------------------------------------------- */
291
- .btn-group {
292
- display: inline-flex;
293
- }
294
- .btn-group .btn {
295
- border-radius: 0;
296
- }
297
- .btn-group .btn:first-child { border-top-left-radius: var(--radius-pill); border-bottom-left-radius: var(--radius-pill); }
298
- .btn-group .btn:last-child { border-top-right-radius: var(--radius-pill); border-bottom-right-radius: var(--radius-pill); }
299
- .btn-group .btn + .btn { margin-left: -1px; }
300
- .btn-group .btn:focus-visible,
301
- .btn-group .btn:hover { z-index: 1; }
1
+ /* ============================================================
2
+ BUTTONS — ATTRUS Design System
3
+ Component CSS — pure tokens, no hardcoded values.
4
+
5
+ Anatomy:
6
+ [leading icon?] [label] [trailing icon?]
7
+
8
+ Variants (semantic):
9
+ .btn-primary | brand fill — main action
10
+ .btn-secondary | neutral fill + hairline border — alternative actions
11
+ .btn-tertiary | soft neutral fill (borderless) — quiet affirmative
12
+ .btn-ghost | no chrome — toolbar / cancel / low priority
13
+ .btn-link | inline text-link styling
14
+ .btn-danger | destructive solid — irreversible
15
+ .btn-danger-outline | destructive secondary — contextual remove
16
+ .btn-success | strong positive — approve / confirm
17
+ .btn-inverse | white pill on dark/brand surfaces
18
+
19
+ Modifier:
20
+ .btn-icon | icon-only (square hit-area)
21
+
22
+ Sizes:
23
+ .btn-xs (24h) .btn-sm (32h) default (40h) .btn-lg (48h)
24
+
25
+ States: default | :hover | :active | :focus-visible | [disabled] | .is-loading
26
+ ============================================================ */
27
+
28
+
29
+ /* ----------------------------------------------------------------
30
+ 1. BASE
31
+ ---------------------------------------------------------------- */
32
+ .btn {
33
+ /* Layout */
34
+ display: inline-flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ gap: var(--space-2);
38
+ white-space: nowrap;
39
+ flex-shrink: 0;
40
+
41
+ /* Box — default size = md (40px tall) */
42
+ height: 40px;
43
+ padding: 0 var(--space-5);
44
+ border: 1px solid transparent;
45
+ border-radius: var(--radius-pill);
46
+
47
+ /* Type */
48
+ font-family: var(--font-sans);
49
+ font-size: var(--fs-md);
50
+ font-weight: var(--w-semibold);
51
+ line-height: 1;
52
+ letter-spacing: var(--ls-body);
53
+
54
+ /* Behavior */
55
+ cursor: pointer;
56
+ user-select: none;
57
+ -webkit-tap-highlight-color: transparent;
58
+ background: transparent;
59
+ color: inherit;
60
+
61
+ /* Motion */
62
+ transition:
63
+ background var(--transition-fast),
64
+ border-color var(--transition-fast),
65
+ color var(--transition-fast),
66
+ box-shadow var(--transition-fast),
67
+ transform var(--transition-fast);
68
+ }
69
+
70
+ .btn:active { transform: translateY(0.5px); }
71
+
72
+ .btn:focus { outline: none; }
73
+ .btn:focus-visible { box-shadow: var(--shadow-focus); }
74
+
75
+ .btn[disabled],
76
+ .btn.is-disabled {
77
+ cursor: not-allowed;
78
+ opacity: 0.5;
79
+ pointer-events: none;
80
+ }
81
+
82
+ /* Icon sizing inside buttons (Lucide replaces <i> with <svg>) */
83
+ .btn > i, .btn > svg,
84
+ .btn > svg {
85
+ width: var(--icon-sm);
86
+ height: var(--icon-sm);
87
+ stroke-width: var(--icon-stroke);
88
+ flex-shrink: 0;
89
+ }
90
+
91
+
92
+ /* ----------------------------------------------------------------
93
+ 2. SIZES
94
+ ---------------------------------------------------------------- */
95
+ .btn-xs {
96
+ height: 24px;
97
+ padding: 0 var(--space-3);
98
+ font-size: var(--fs-sm);
99
+ gap: var(--space-1);
100
+ }
101
+ .btn-xs > i, .btn-xs > svg, .btn-xs > svg { width: var(--icon-xs); height: var(--icon-xs); }
102
+
103
+ .btn-sm {
104
+ height: 32px;
105
+ padding: 0 var(--space-4);
106
+ font-size: var(--fs-md);
107
+ }
108
+ .btn-sm > i, .btn-sm > svg, .btn-sm > svg { width: var(--icon-xs); height: var(--icon-xs); }
109
+
110
+ /* default = md (40px) — defined in .btn */
111
+
112
+ .btn-lg {
113
+ height: 48px;
114
+ padding: 0 var(--space-6);
115
+ font-size: var(--fs-base);
116
+ gap: var(--space-3);
117
+ }
118
+ .btn-lg > i, .btn-lg > svg, .btn-lg > svg { width: var(--icon-md); height: var(--icon-md); }
119
+
120
+
121
+ /* ----------------------------------------------------------------
122
+ 3. ICON-ONLY MODIFIER
123
+ ---------------------------------------------------------------- */
124
+ .btn-icon { padding: 0; aspect-ratio: 1 / 1; }
125
+ .btn-icon.btn-xs { width: 24px; }
126
+ .btn-icon.btn-sm { width: 32px; }
127
+ .btn-icon { width: 40px; }
128
+ .btn-icon.btn-lg { width: 48px; }
129
+
130
+
131
+ /* ----------------------------------------------------------------
132
+ 4. VARIANTS
133
+ ---------------------------------------------------------------- */
134
+
135
+ /* PRIMARY — brand-blue solid */
136
+ .btn-primary {
137
+ background: var(--color-brand-secondary-500);
138
+ color: var(--color-foreground-on-solid);
139
+ }
140
+ .btn-primary:hover { background: var(--color-brand-secondary-600); }
141
+ .btn-primary:active { background: var(--color-brand-secondary-700); }
142
+ .btn-primary:focus-visible { box-shadow: var(--shadow-focus); }
143
+
144
+
145
+ /* SECONDARY — soft neutral fill + hairline border */
146
+ .btn-secondary {
147
+ background: var(--color-background-subtle);
148
+ color: var(--color-foreground-default);
149
+ border-color: var(--color-border-default);
150
+ }
151
+ .btn-secondary:hover { background: var(--color-background-hover); border-color: var(--color-border-strong); }
152
+ .btn-secondary:active { background: var(--color-background-press); }
153
+ .btn-secondary:focus-visible { box-shadow: var(--shadow-focus); }
154
+
155
+
156
+ /* TERTIARY — soft neutral fill, borderless (quieter than secondary) */
157
+ .btn-tertiary {
158
+ background: color-mix(in srgb, var(--color-foreground-default) 7%, transparent);
159
+ color: var(--color-foreground-default);
160
+ border-color: transparent;
161
+ }
162
+ .btn-tertiary:hover { background: color-mix(in srgb, var(--color-foreground-default) 12%, transparent); }
163
+ .btn-tertiary:active { background: color-mix(in srgb, var(--color-foreground-default) 16%, transparent); }
164
+ .btn-tertiary:focus-visible { box-shadow: var(--shadow-focus); }
165
+
166
+
167
+ /* GHOST — no chrome until hover */
168
+ .btn-ghost {
169
+ background: transparent;
170
+ color: var(--color-foreground-secondary);
171
+ }
172
+ .btn-ghost:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
173
+ .btn-ghost:active { background: var(--color-background-press); }
174
+ .btn-ghost:focus-visible { box-shadow: var(--shadow-focus); }
175
+
176
+
177
+ /* LINK — looks like inline text link */
178
+ .btn-link {
179
+ background: transparent;
180
+ color: var(--color-foreground-highlight);
181
+ height: auto;
182
+ padding: 0;
183
+ border: 0;
184
+ border-radius: var(--radius-2xs);
185
+ text-decoration: none;
186
+ }
187
+ .btn-link:hover { color: var(--color-brand-secondary-700); text-decoration: underline; text-underline-offset: 2px; }
188
+ .btn-link:active { color: var(--color-brand-secondary-800); }
189
+ .btn-link:focus-visible { box-shadow: var(--shadow-focus); }
190
+
191
+
192
+ /* DANGER — destructive solid */
193
+ .btn-danger {
194
+ background: var(--color-danger-500);
195
+ color: var(--color-foreground-on-solid);
196
+ }
197
+ .btn-danger:hover { background: var(--color-danger-700); }
198
+ .btn-danger:active { background: var(--color-danger-900); }
199
+ .btn-danger:focus-visible { box-shadow: var(--shadow-focus-danger); }
200
+
201
+
202
+ /* DANGER OUTLINE — destructive contextual */
203
+ .btn-danger-outline {
204
+ background: var(--color-background-surface);
205
+ color: var(--color-danger-500);
206
+ border-color: var(--color-feedback-danger-border);
207
+ }
208
+ .btn-danger-outline:hover { background: var(--color-feedback-danger-background); border-color: var(--color-danger-500); }
209
+ .btn-danger-outline:active { background: color-mix(in srgb, var(--color-feedback-danger-background) 85%, var(--color-danger-500)); }
210
+ .btn-danger-outline:focus-visible { box-shadow: var(--shadow-focus-danger); }
211
+
212
+
213
+ /* SUCCESS — strong positive (rare) */
214
+ .btn-success {
215
+ background: var(--color-success-500);
216
+ color: var(--color-foreground-on-solid);
217
+ }
218
+ .btn-success:hover { background: var(--color-success-700); }
219
+ .btn-success:active { background: var(--color-success-900); }
220
+ .btn-success:focus-visible { box-shadow: var(--shadow-focus-success); }
221
+
222
+
223
+ /* INVERSE — white pill on dark/brand surfaces */
224
+ .btn-inverse {
225
+ background: var(--color-pure-white);
226
+ color: var(--color-brand-secondary-700);
227
+ }
228
+ .btn-inverse:hover { background: var(--color-neutral-100); color: var(--color-brand-secondary-800); }
229
+ .btn-inverse:active { background: var(--color-neutral-150); }
230
+ .btn-inverse:focus-visible { box-shadow: var(--shadow-focus-sky); }
231
+
232
+
233
+ /* ----------------------------------------------------------------
234
+ 5. ON-INVERSE GHOSTS — for use inside .inverse / dark surfaces
235
+ Apply BOTH .btn-ghost AND .on-inverse
236
+ ---------------------------------------------------------------- */
237
+ .btn-ghost.on-inverse {
238
+ color: var(--color-foreground-inverse-secondary);
239
+ }
240
+ .btn-ghost.on-inverse:hover { background: var(--color-background-inverse-hover); color: var(--color-foreground-inverse-default); }
241
+ .btn-ghost.on-inverse:active { background: var(--color-background-inverse-press); }
242
+
243
+ /* Outline ghost on inverse — pairs with .btn-secondary semantics */
244
+ .btn-secondary.on-inverse {
245
+ background: transparent;
246
+ color: var(--color-foreground-inverse-default);
247
+ border-color: var(--color-border-inverse-strong);
248
+ }
249
+ .btn-secondary.on-inverse:hover { background: var(--color-background-inverse-hover); border-color: var(--color-foreground-inverse-secondary); }
250
+ .btn-secondary.on-inverse:active { background: var(--color-background-inverse-press); }
251
+
252
+
253
+ /* ----------------------------------------------------------------
254
+ 6. LOADING STATE
255
+ Add .is-loading; keeps current width but hides label/icons,
256
+ shows a centered spinner.
257
+ ---------------------------------------------------------------- */
258
+ .btn.is-loading {
259
+ position: relative;
260
+ color: transparent !important;
261
+ pointer-events: none;
262
+ }
263
+ .btn.is-loading > * { visibility: hidden; }
264
+ .btn.is-loading::after {
265
+ content: "";
266
+ position: absolute;
267
+ top: 50%; left: 50%;
268
+ width: var(--icon-sm);
269
+ height: var(--icon-sm);
270
+ margin: calc(var(--icon-sm) / -2) 0 0 calc(var(--icon-sm) / -2);
271
+ border: 2px solid currentColor;
272
+ border-top-color: transparent;
273
+ border-radius: var(--radius-pill);
274
+ animation: btn-spin var(--duration-slower) linear infinite;
275
+ /* Re-introduce currentColor where the variant set it; ::after inherits the parent's color BEFORE the !important override */
276
+ color: var(--color-foreground-default);
277
+ }
278
+ .btn-primary.is-loading::after,
279
+ .btn-danger.is-loading::after,
280
+ .btn-success.is-loading::after { color: var(--color-pure-white); }
281
+ .btn-tertiary.is-loading::after { color: var(--color-foreground-default); }
282
+
283
+ @keyframes btn-spin {
284
+ to { transform: rotate(360deg); }
285
+ }
286
+
287
+
288
+ /* ----------------------------------------------------------------
289
+ 7. BUTTON GROUP — segmented / toolbar
290
+ ---------------------------------------------------------------- */
291
+ .btn-group {
292
+ display: inline-flex;
293
+ }
294
+ .btn-group .btn {
295
+ border-radius: 0;
296
+ }
297
+ .btn-group .btn:first-child { border-top-left-radius: var(--radius-pill); border-bottom-left-radius: var(--radius-pill); }
298
+ .btn-group .btn:last-child { border-top-right-radius: var(--radius-pill); border-bottom-right-radius: var(--radius-pill); }
299
+ .btn-group .btn + .btn { margin-left: -1px; }
300
+ .btn-group .btn:focus-visible,
301
+ .btn-group .btn:hover { z-index: 1; }