@kwtv/components 0.2.0 → 0.4.0

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 (56) hide show
  1. package/README.md +52 -9
  2. package/package.json +2 -2
  3. package/src/astro/Accordion.astro +10 -6
  4. package/src/astro/AccordionItem.astro +55 -45
  5. package/src/astro/ActiveFilterTags.astro +41 -28
  6. package/src/astro/Alert.astro +45 -41
  7. package/src/astro/Badge.astro +80 -76
  8. package/src/astro/Band.astro +104 -58
  9. package/src/astro/Breadcrumb.astro +38 -30
  10. package/src/astro/Button.astro +135 -129
  11. package/src/astro/Card.astro +72 -62
  12. package/src/astro/CartLine.astro +72 -58
  13. package/src/astro/Checkbox.astro +36 -32
  14. package/src/astro/Chip.astro +68 -64
  15. package/src/astro/ChipRail.astro +6 -1
  16. package/src/astro/DotGrid.astro +55 -42
  17. package/src/astro/EmptyState.astro +29 -23
  18. package/src/astro/FilterGroup.astro +29 -26
  19. package/src/astro/Filters.astro +57 -47
  20. package/src/astro/Footer.astro +195 -178
  21. package/src/astro/FooterColumn.astro +39 -35
  22. package/src/astro/Header.astro +80 -67
  23. package/src/astro/Icon.astro +4 -2
  24. package/src/astro/IconButton.astro +67 -61
  25. package/src/astro/Marquee.astro +43 -41
  26. package/src/astro/NavDropdown.astro +169 -149
  27. package/src/astro/NavLink.astro +59 -56
  28. package/src/astro/Newsletter.astro +64 -56
  29. package/src/astro/Pager.astro +71 -46
  30. package/src/astro/PickCard.astro +72 -68
  31. package/src/astro/ProductCard.astro +134 -119
  32. package/src/astro/Radio.astro +36 -32
  33. package/src/astro/Skeleton.astro +60 -42
  34. package/src/astro/Social.astro +26 -21
  35. package/src/astro/StepIndicator.astro +70 -54
  36. package/src/astro/Sticker.astro +74 -49
  37. package/src/astro/Swatch.astro +44 -40
  38. package/src/astro/SwipeCarousel.astro +5 -1
  39. package/src/astro/Table.astro +66 -56
  40. package/src/astro/TableCard.astro +45 -37
  41. package/src/astro/TextField.astro +104 -91
  42. package/src/astro/Totals.astro +39 -35
  43. package/src/react/Drawer.css +60 -56
  44. package/src/react/Drawer.tsx +1 -5
  45. package/src/react/Modal.css +97 -93
  46. package/src/react/Modal.tsx +4 -6
  47. package/src/react/NewsCarousel.css +108 -104
  48. package/src/react/NewsCarousel.tsx +7 -1
  49. package/src/react/QuantityStepper.css +84 -80
  50. package/src/react/Select.css +159 -155
  51. package/src/react/Select.tsx +8 -2
  52. package/src/react/Toast.css +128 -124
  53. package/src/react/Toast.tsx +38 -4
  54. package/src/styles/base.css +30 -26
  55. package/src/styles/controls.css +183 -179
  56. package/src/styles/motion.css +203 -199
@@ -1,93 +1,97 @@
1
1
  /* Quantity stepper. All values from the design-system tokens. */
2
2
 
3
- .ui-qty {
4
- display: inline-flex;
5
- align-items: center;
6
- border: 1px solid var(--border);
7
- border-radius: var(--radius-md);
8
- background: var(--surface);
9
- overflow: hidden;
10
- }
3
+ @layer reset, tokens, base, components, motion, utilities;
11
4
 
12
- .ui-qty button {
13
- display: inline-flex;
14
- align-items: center;
15
- justify-content: center;
16
- width: 40px;
17
- height: 40px;
18
- background: none;
19
- border: 0;
20
- color: var(--text);
21
- font-size: var(--text-lg);
22
- line-height: 1;
23
- cursor: pointer;
24
- transition: background-color var(--duration-fast) var(--ease-standard);
25
- }
5
+ @layer components {
6
+ .ui-qty {
7
+ display: inline-flex;
8
+ align-items: center;
9
+ border: 1px solid var(--border);
10
+ border-radius: var(--radius-md);
11
+ background: var(--surface);
12
+ overflow: hidden;
13
+ }
26
14
 
27
- .ui-qty button:hover:not(:disabled) {
28
- background: var(--surface-hover);
29
- }
15
+ .ui-qty button {
16
+ display: inline-flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ width: 40px;
20
+ height: 40px;
21
+ background: none;
22
+ border: 0;
23
+ color: var(--text);
24
+ font-size: var(--text-lg);
25
+ line-height: 1;
26
+ cursor: pointer;
27
+ transition: background-color var(--duration-fast) var(--ease-standard);
28
+ }
30
29
 
31
- .ui-qty button:focus-visible {
32
- outline: 2px solid var(--focus);
33
- outline-offset: -2px;
34
- }
30
+ .ui-qty button:hover:not(:disabled) {
31
+ background: var(--surface-hover);
32
+ }
35
33
 
36
- .ui-qty button:disabled {
37
- opacity: 0.4;
38
- cursor: not-allowed;
39
- }
34
+ .ui-qty button:focus-visible {
35
+ outline: 2px solid var(--focus);
36
+ outline-offset: -2px;
37
+ }
40
38
 
41
- /* The bin, shown instead of minus when the last one would be removed.
42
- --danger, because it destroys the line rather than decrementing it. The
43
- icon is not the only signal: the button carries an aria-label, and hover
44
- fills it so the intent is obvious before the click. */
45
- /* `.ui-qty button` is (0,1,1) and would otherwise win over a lone class, so
46
- these are scoped the same way rather than reaching for !important. */
47
- .ui-qty button.ui-qty__remove {
48
- color: var(--danger);
49
- }
50
- .ui-qty button.ui-qty__remove:hover:not(:disabled) {
51
- background: var(--danger-bg);
52
- }
53
- .ui-qty__remove svg {
54
- display: block;
55
- }
39
+ .ui-qty button:disabled {
40
+ opacity: 0.4;
41
+ cursor: not-allowed;
42
+ }
56
43
 
57
- .ui-qty__val {
58
- min-width: 40px;
59
- text-align: center;
60
- font-size: var(--text-sm);
61
- font-weight: var(--weight-semibold);
62
- font-variant-numeric: tabular-nums;
63
- color: var(--text);
64
- }
44
+ /* The bin, shown instead of minus when the last one would be removed.
45
+ --danger, because it destroys the line rather than decrementing it. The
46
+ icon is not the only signal: the button carries an aria-label, and hover
47
+ fills it so the intent is obvious before the click. */
48
+ /* `.ui-qty button` is (0,1,1) and would otherwise win over a lone class, so
49
+ these are scoped the same way rather than reaching for !important. */
50
+ .ui-qty button.ui-qty__remove {
51
+ color: var(--danger);
52
+ }
53
+ .ui-qty button.ui-qty__remove:hover:not(:disabled) {
54
+ background: var(--danger-bg);
55
+ }
56
+ .ui-qty__remove svg {
57
+ display: block;
58
+ }
65
59
 
66
- /* Large, 48 px, for the product page. */
67
- .ui-qty--lg button {
68
- width: 48px;
69
- height: 48px;
70
- font-size: var(--text-xl);
71
- }
72
- .ui-qty--lg .ui-qty__val {
73
- min-width: 48px;
74
- font-size: var(--text-base);
75
- }
60
+ .ui-qty__val {
61
+ min-width: 40px;
62
+ text-align: center;
63
+ font-size: var(--text-sm);
64
+ font-weight: var(--weight-semibold);
65
+ font-variant-numeric: tabular-nums;
66
+ color: var(--text);
67
+ }
76
68
 
77
- .ui-sr-only {
78
- position: absolute;
79
- width: 1px;
80
- height: 1px;
81
- padding: 0;
82
- margin: -1px;
83
- overflow: hidden;
84
- clip-path: inset(50%);
85
- white-space: nowrap;
86
- border: 0;
87
- }
69
+ /* Large, 48 px, for the product page. */
70
+ .ui-qty--lg button {
71
+ width: 48px;
72
+ height: 48px;
73
+ font-size: var(--text-xl);
74
+ }
75
+ .ui-qty--lg .ui-qty__val {
76
+ min-width: 48px;
77
+ font-size: var(--text-base);
78
+ }
88
79
 
89
- @media (prefers-reduced-motion: reduce) {
90
- .ui-qty button {
91
- transition: none;
80
+ .ui-sr-only {
81
+ position: absolute;
82
+ width: 1px;
83
+ height: 1px;
84
+ padding: 0;
85
+ margin: -1px;
86
+ overflow: hidden;
87
+ clip-path: inset(50%);
88
+ white-space: nowrap;
89
+ border: 0;
90
+ }
91
+
92
+ @media (prefers-reduced-motion: reduce) {
93
+ .ui-qty button {
94
+ transition: none;
95
+ }
92
96
  }
93
97
  }
@@ -1,183 +1,187 @@
1
1
  /* Select. Trigger plus listbox. All values from the design-system tokens. */
2
2
 
3
- .ui-field {
4
- display: flex;
5
- flex-direction: column;
6
- gap: var(--space-1-5);
7
- }
3
+ @layer reset, tokens, base, components, motion, utilities;
8
4
 
9
- .ui-field__label {
10
- font-size: var(--text-sm);
11
- font-weight: var(--weight-medium);
12
- color: var(--text);
13
- }
5
+ @layer components {
6
+ .ui-field {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: var(--space-1-5);
10
+ }
14
11
 
15
- .ui-field__req {
16
- color: var(--danger);
17
- margin-inline-start: var(--space-0-5);
18
- }
12
+ .ui-field__label {
13
+ font-size: var(--text-sm);
14
+ font-weight: var(--weight-medium);
15
+ color: var(--text);
16
+ }
19
17
 
20
- .ui-field__hint {
21
- font-size: var(--text-xs);
22
- line-height: var(--leading-xs);
23
- color: var(--text-muted);
24
- }
18
+ .ui-field__req {
19
+ color: var(--danger);
20
+ margin-inline-start: var(--space-0-5);
21
+ }
25
22
 
26
- .ui-field__error {
27
- display: inline-flex;
28
- align-items: center;
29
- gap: var(--space-1-5);
30
- font-size: var(--text-xs);
31
- line-height: var(--leading-xs);
32
- color: var(--danger);
33
- }
23
+ .ui-field__hint {
24
+ font-size: var(--text-xs);
25
+ line-height: var(--leading-xs);
26
+ color: var(--text-muted);
27
+ }
34
28
 
35
- .ui-select {
36
- position: relative;
37
- display: block;
38
- }
29
+ .ui-field__error {
30
+ display: inline-flex;
31
+ align-items: center;
32
+ gap: var(--space-1-5);
33
+ font-size: var(--text-xs);
34
+ line-height: var(--leading-xs);
35
+ color: var(--danger);
36
+ }
39
37
 
40
- .ui-select__btn {
41
- display: flex;
42
- align-items: center;
43
- justify-content: space-between;
44
- gap: var(--space-2);
45
- width: 100%;
46
- min-height: 40px;
47
- padding: 0 var(--space-3);
48
- background: var(--surface);
49
- border: 1px solid var(--border-strong);
50
- border-radius: var(--radius-md);
51
- color: var(--text);
52
- font-family: var(--font-sans);
53
- font-size: var(--text-sm);
54
- text-align: start;
55
- cursor: pointer;
56
- transition: border-color var(--duration-fast) var(--ease-standard);
57
- }
38
+ .ui-select {
39
+ position: relative;
40
+ display: block;
41
+ }
58
42
 
59
- .ui-select__btn:hover,
60
- .ui-select__btn.is-hover {
61
- border-color: var(--border-strong);
62
- }
43
+ .ui-select__btn {
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: space-between;
47
+ gap: var(--space-2);
48
+ width: 100%;
49
+ min-height: 40px;
50
+ padding: 0 var(--space-3);
51
+ background: var(--surface);
52
+ border: 1px solid var(--border-strong);
53
+ border-radius: var(--radius-md);
54
+ color: var(--text);
55
+ font-family: var(--font-sans);
56
+ font-size: var(--text-sm);
57
+ text-align: start;
58
+ cursor: pointer;
59
+ transition: border-color var(--duration-fast) var(--ease-standard);
60
+ }
63
61
 
64
- .ui-select__btn:focus-visible,
65
- .ui-select__btn.is-focus {
66
- outline: 2px solid var(--focus);
67
- outline-offset: 2px;
68
- box-shadow: 0 0 0 4px var(--focus-outer);
69
- }
62
+ .ui-select__btn:hover,
63
+ .ui-select__btn.is-hover {
64
+ border-color: var(--border-strong);
65
+ }
70
66
 
71
- .ui-select__btn:disabled {
72
- opacity: 0.5;
73
- cursor: not-allowed;
74
- }
67
+ .ui-select__btn:focus-visible,
68
+ .ui-select__btn.is-focus {
69
+ outline: 2px solid var(--focus);
70
+ outline-offset: 2px;
71
+ box-shadow: 0 0 0 4px var(--focus-outer);
72
+ }
75
73
 
76
- .ui-select__value {
77
- overflow: hidden;
78
- text-overflow: ellipsis;
79
- white-space: nowrap;
80
- }
74
+ .ui-select__btn:disabled {
75
+ opacity: 0.5;
76
+ cursor: not-allowed;
77
+ }
81
78
 
82
- .ui-select__value--empty {
83
- color: var(--text-muted);
84
- }
79
+ .ui-select__value {
80
+ overflow: hidden;
81
+ text-overflow: ellipsis;
82
+ white-space: nowrap;
83
+ }
85
84
 
86
- .ui-select__chev {
87
- display: inline-flex;
88
- flex: none;
89
- color: var(--text-muted);
90
- }
85
+ .ui-select__value--empty {
86
+ color: var(--text-muted);
87
+ }
91
88
 
92
- /* Invalid: 2 px danger border. */
93
- .ui-select--invalid .ui-select__btn {
94
- border-width: 2px;
95
- border-color: var(--danger);
96
- padding-inline: calc(var(--space-3) - 1px);
97
- }
89
+ .ui-select__chev {
90
+ display: inline-flex;
91
+ flex: none;
92
+ color: var(--text-muted);
93
+ }
98
94
 
99
- /* The list. 6 px offset, 272 px max height, 36 px rows. */
100
- .ui-select__list {
101
- position: absolute;
102
- top: calc(100% + var(--space-1-5));
103
- left: 0;
104
- right: 0;
105
- z-index: var(--z-dropdown);
106
- max-height: 272px;
107
- overflow-y: auto;
108
- padding: var(--space-1);
109
- background: var(--surface);
110
- border: 1px solid var(--border);
111
- border-radius: var(--radius-md);
112
- box-shadow: var(--shadow-lg);
113
- }
95
+ /* Invalid: 2 px danger border. */
96
+ .ui-select--invalid .ui-select__btn {
97
+ border-width: 2px;
98
+ border-color: var(--danger);
99
+ padding-inline: calc(var(--space-3) - 1px);
100
+ }
114
101
 
115
- .ui-select__opt {
116
- display: flex;
117
- align-items: center;
118
- gap: var(--space-2);
119
- width: 100%;
120
- min-height: 36px;
121
- padding: 0 var(--space-2);
122
- background: transparent;
123
- border: 0;
124
- border-radius: var(--radius-sm);
125
- color: var(--text);
126
- font-family: var(--font-sans);
127
- font-size: var(--text-sm);
128
- text-align: start;
129
- cursor: pointer;
130
- }
102
+ /* The list. 6 px offset, 272 px max height, 36 px rows. */
103
+ .ui-select__list {
104
+ position: absolute;
105
+ top: calc(100% + var(--space-1-5));
106
+ left: 0;
107
+ right: 0;
108
+ z-index: var(--z-dropdown);
109
+ max-height: 272px;
110
+ overflow-y: auto;
111
+ padding: var(--space-1);
112
+ background: var(--surface);
113
+ border: 1px solid var(--border);
114
+ border-radius: var(--radius-md);
115
+ box-shadow: var(--shadow-lg);
116
+ }
131
117
 
132
- .ui-select__opt:hover,
133
- .ui-select__opt.is-hover,
134
- .ui-select__opt.is-focus {
135
- background: var(--surface-hover);
136
- }
118
+ .ui-select__opt {
119
+ display: flex;
120
+ align-items: center;
121
+ gap: var(--space-2);
122
+ width: 100%;
123
+ min-height: 36px;
124
+ padding: 0 var(--space-2);
125
+ background: transparent;
126
+ border: 0;
127
+ border-radius: var(--radius-sm);
128
+ color: var(--text);
129
+ font-family: var(--font-sans);
130
+ font-size: var(--text-sm);
131
+ text-align: start;
132
+ cursor: pointer;
133
+ }
137
134
 
138
- /* Selection marked by the tick and a weight change, not the fill alone. */
139
- .ui-select__opt[aria-selected="true"] {
140
- font-weight: var(--weight-semibold);
141
- }
135
+ .ui-select__opt:hover,
136
+ .ui-select__opt.is-hover,
137
+ .ui-select__opt.is-focus {
138
+ background: var(--surface-hover);
139
+ }
142
140
 
143
- .ui-select__tick {
144
- display: inline-flex;
145
- flex: none;
146
- width: 13px;
147
- color: var(--primary);
148
- visibility: hidden;
149
- }
141
+ /* Selection marked by the tick and a weight change, not the fill alone. */
142
+ .ui-select__opt[aria-selected="true"] {
143
+ font-weight: var(--weight-semibold);
144
+ }
150
145
 
151
- .ui-select__opt[aria-selected="true"] .ui-select__tick {
152
- visibility: visible;
153
- }
146
+ .ui-select__tick {
147
+ display: inline-flex;
148
+ flex: none;
149
+ width: 13px;
150
+ color: var(--primary);
151
+ visibility: hidden;
152
+ }
154
153
 
155
- .ui-select__opt.is-disabled,
156
- .ui-select__opt[aria-disabled="true"] {
157
- opacity: 0.5;
158
- cursor: not-allowed;
159
- }
154
+ .ui-select__opt[aria-selected="true"] .ui-select__tick {
155
+ visibility: visible;
156
+ }
160
157
 
161
- /* Group headings. Not selectable. */
162
- .ui-select__grouplabel {
163
- display: block;
164
- padding: var(--space-2) var(--space-2) var(--space-1);
165
- font-family: var(--font-mono);
166
- font-size: var(--text-2xs);
167
- font-weight: var(--weight-semibold);
168
- letter-spacing: var(--tracking-wider);
169
- text-transform: uppercase;
170
- color: var(--text-muted);
171
- }
158
+ .ui-select__opt.is-disabled,
159
+ .ui-select__opt[aria-disabled="true"] {
160
+ opacity: 0.5;
161
+ cursor: not-allowed;
162
+ }
172
163
 
173
- @media (max-width: 700px), (pointer: coarse) {
174
- .ui-select__opt {
175
- min-height: 40px;
164
+ /* Group headings. Not selectable. */
165
+ .ui-select__grouplabel {
166
+ display: block;
167
+ padding: var(--space-2) var(--space-2) var(--space-1);
168
+ font-family: var(--font-mono);
169
+ font-size: var(--text-2xs);
170
+ font-weight: var(--weight-semibold);
171
+ letter-spacing: var(--tracking-wider);
172
+ text-transform: uppercase;
173
+ color: var(--text-muted);
176
174
  }
177
- }
178
175
 
179
- @media (prefers-reduced-motion: reduce) {
180
- .ui-select__btn {
181
- transition: none;
176
+ @media (max-width: 700px), (pointer: coarse) {
177
+ .ui-select__opt {
178
+ min-height: 40px;
179
+ }
180
+ }
181
+
182
+ @media (prefers-reduced-motion: reduce) {
183
+ .ui-select__btn {
184
+ transition: none;
185
+ }
182
186
  }
183
187
  }
@@ -129,7 +129,9 @@ export default function Select({
129
129
  const openList = useCallback(() => {
130
130
  if (disabled) return;
131
131
  setOpen(true);
132
- const current = selectable.findIndex((o) => o.value === value && !o.disabled);
132
+ const current = selectable.findIndex(
133
+ (o) => o.value === value && !o.disabled,
134
+ );
133
135
  const firstEnabled = selectable.findIndex((o) => !o.disabled);
134
136
  setActiveIndex(current >= 0 ? current : firstEnabled);
135
137
  }, [disabled, selectable, value]);
@@ -236,7 +238,11 @@ export default function Select({
236
238
 
237
239
  const describedBy = [errorId, hintId].filter(Boolean).join(" ") || undefined;
238
240
 
239
- const fieldClasses = ["ui-select", invalid && "ui-select--invalid", open && "is-open"]
241
+ const fieldClasses = [
242
+ "ui-select",
243
+ invalid && "ui-select--invalid",
244
+ open && "is-open",
245
+ ]
240
246
  .filter(Boolean)
241
247
  .join(" ");
242
248