@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
@@ -68,17 +68,21 @@ const hasNext = current < total;
68
68
  <Icon name="chevron-left" size={17} stroke={2.3} />
69
69
  </a>
70
70
  ) : (
71
- <span class="is-disabled" aria-hidden="true"><Icon name="chevron-left" size={17} stroke={2.3} /></span>
71
+ <span class="is-disabled" aria-hidden="true">
72
+ <Icon name="chevron-left" size={17} stroke={2.3} />
73
+ </span>
72
74
  )}
73
75
 
74
76
  {list.map((p) =>
75
77
  p === "…" ? (
76
78
  <span class="ui-pager__gap">…</span>
77
79
  ) : p === current ? (
78
- <a class="is-current" href={href(p)} aria-current="page">{p}</a>
80
+ <a class="is-current" href={href(p)} aria-current="page">
81
+ {p}
82
+ </a>
79
83
  ) : (
80
84
  <a href={href(p)}>{p}</a>
81
- )
85
+ ),
82
86
  )}
83
87
 
84
88
  {hasNext ? (
@@ -86,7 +90,9 @@ const hasNext = current < total;
86
90
  <Icon name="chevron-right" size={17} stroke={2.3} />
87
91
  </a>
88
92
  ) : (
89
- <span class="is-disabled" aria-hidden="true"><Icon name="chevron-right" size={17} stroke={2.3} /></span>
93
+ <span class="is-disabled" aria-hidden="true">
94
+ <Icon name="chevron-right" size={17} stroke={2.3} />
95
+ </span>
90
96
  )}
91
97
  </nav>
92
98
 
@@ -119,13 +125,24 @@ const hasNext = current < total;
119
125
  const nav = a.closest("[data-ui-pager][data-ui-pager-preserve]");
120
126
  if (!nav) return;
121
127
  const me = e as MouseEvent;
122
- if (me.button !== 0 || me.metaKey || me.ctrlKey || me.shiftKey || me.altKey) return;
128
+ if (
129
+ me.button !== 0 ||
130
+ me.metaKey ||
131
+ me.ctrlKey ||
132
+ me.shiftKey ||
133
+ me.altKey
134
+ )
135
+ return;
123
136
  const { page, url } = pageFrom(nav, a);
124
137
  if (page == null) return;
125
138
  e.preventDefault();
126
139
  e.stopImmediatePropagation();
127
140
  history.pushState({ uiPager: true, page }, "", url.href);
128
- window.dispatchEvent(new CustomEvent("ui:pagerchange", { detail: { page, url: url.href } }));
141
+ window.dispatchEvent(
142
+ new CustomEvent("ui:pagerchange", {
143
+ detail: { page, url: url.href },
144
+ }),
145
+ );
129
146
  },
130
147
  true,
131
148
  );
@@ -136,7 +153,11 @@ const hasNext = current < total;
136
153
  const p = parseInt(u.searchParams.get("page") || "1", 10);
137
154
  window.dispatchEvent(
138
155
  new CustomEvent("ui:pagerchange", {
139
- detail: { page: Number.isFinite(p) ? p : 1, url: u.href, popped: true },
156
+ detail: {
157
+ page: Number.isFinite(p) ? p : 1,
158
+ url: u.href,
159
+ popped: true,
160
+ },
140
161
  }),
141
162
  );
142
163
  });
@@ -147,49 +168,53 @@ const hasNext = current < total;
147
168
  </script>
148
169
 
149
170
  <style>
150
- .ui-pager {
151
- display: flex;
152
- align-items: center;
153
- gap: var(--space-1);
154
- font-size: var(--text-sm);
155
- }
171
+ @layer reset, tokens, base, components, motion, utilities;
172
+
173
+ @layer components {
174
+ .ui-pager {
175
+ display: flex;
176
+ align-items: center;
177
+ gap: var(--space-1);
178
+ font-size: var(--text-sm);
179
+ }
156
180
 
157
- .ui-pager a,
158
- .ui-pager span:not(.ui-pager__gap) {
159
- display: inline-flex;
160
- align-items: center;
161
- justify-content: center;
162
- min-width: 36px;
163
- height: 36px;
164
- padding: 0 var(--space-1);
165
- border-radius: var(--radius-md);
166
- color: var(--link);
167
- text-decoration: none;
168
- }
181
+ .ui-pager a,
182
+ .ui-pager span:not(.ui-pager__gap) {
183
+ display: inline-flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ min-width: 36px;
187
+ height: 36px;
188
+ padding: 0 var(--space-1);
189
+ border-radius: var(--radius-md);
190
+ color: var(--link);
191
+ text-decoration: none;
192
+ }
169
193
 
170
- .ui-pager a:hover {
171
- background: var(--surface-hover);
172
- color: var(--link-hover);
173
- }
174
- .ui-pager a:focus-visible {
175
- outline: 2px solid var(--focus);
176
- outline-offset: 2px;
177
- }
194
+ .ui-pager a:hover {
195
+ background: var(--surface-hover);
196
+ color: var(--link-hover);
197
+ }
198
+ .ui-pager a:focus-visible {
199
+ outline: 2px solid var(--focus);
200
+ outline-offset: 2px;
201
+ }
178
202
 
179
- .ui-pager a.is-current {
180
- background: var(--primary);
181
- color: var(--primary-fg);
182
- font-weight: var(--weight-semibold);
183
- }
203
+ .ui-pager a.is-current {
204
+ background: var(--primary);
205
+ color: var(--primary-fg);
206
+ font-weight: var(--weight-semibold);
207
+ }
184
208
 
185
- .ui-pager .is-disabled {
186
- color: var(--border);
187
- cursor: not-allowed;
188
- }
209
+ .ui-pager .is-disabled {
210
+ color: var(--border);
211
+ cursor: not-allowed;
212
+ }
189
213
 
190
- .ui-pager__gap {
191
- min-width: 24px;
192
- text-align: center;
193
- color: var(--text-muted);
214
+ .ui-pager__gap {
215
+ min-width: 24px;
216
+ text-align: center;
217
+ color: var(--text-muted);
218
+ }
194
219
  }
195
220
  </style>
@@ -58,85 +58,89 @@ const classes = [
58
58
  </label>
59
59
 
60
60
  <style>
61
- .ui-pick {
62
- display: flex;
63
- align-items: flex-start;
64
- gap: var(--space-3);
65
- padding: var(--space-4);
66
- background: var(--surface);
67
- border: 1px solid var(--border);
68
- border-radius: var(--radius-lg);
69
- cursor: pointer;
70
- transition:
71
- border-color var(--duration-fast) var(--ease-standard),
72
- background-color var(--duration-fast) var(--ease-standard);
73
- }
61
+ @layer reset, tokens, base, components, motion, utilities;
74
62
 
75
- .ui-pick input {
76
- width: 18px;
77
- height: 18px;
78
- margin-top: 2px;
79
- flex-shrink: 0;
80
- accent-color: var(--primary);
81
- cursor: pointer;
82
- }
63
+ @layer components {
64
+ .ui-pick {
65
+ display: flex;
66
+ align-items: flex-start;
67
+ gap: var(--space-3);
68
+ padding: var(--space-4);
69
+ background: var(--surface);
70
+ border: 1px solid var(--border);
71
+ border-radius: var(--radius-lg);
72
+ cursor: pointer;
73
+ transition:
74
+ border-color var(--duration-fast) var(--ease-standard),
75
+ background-color var(--duration-fast) var(--ease-standard);
76
+ }
83
77
 
84
- .ui-pick input:focus-visible {
85
- outline: 2px solid var(--focus);
86
- outline-offset: 2px;
87
- box-shadow: 0 0 0 4px var(--focus-outer);
88
- }
78
+ .ui-pick input {
79
+ width: 18px;
80
+ height: 18px;
81
+ margin-top: 2px;
82
+ flex-shrink: 0;
83
+ accent-color: var(--primary);
84
+ cursor: pointer;
85
+ }
89
86
 
90
- .ui-pick__text {
91
- display: flex;
92
- flex-direction: column;
93
- gap: var(--space-1);
94
- font-family: var(--font-sans);
95
- }
87
+ .ui-pick input:focus-visible {
88
+ outline: 2px solid var(--focus);
89
+ outline-offset: 2px;
90
+ box-shadow: 0 0 0 4px var(--focus-outer);
91
+ }
96
92
 
97
- .ui-pick__title {
98
- font-size: var(--text-sm);
99
- font-weight: var(--weight-semibold);
100
- color: var(--text);
101
- }
93
+ .ui-pick__text {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: var(--space-1);
97
+ font-family: var(--font-sans);
98
+ }
102
99
 
103
- .ui-pick__note {
104
- font-size: var(--text-xs);
105
- line-height: var(--leading-xs);
106
- color: var(--text-muted);
107
- }
100
+ .ui-pick__title {
101
+ font-size: var(--text-sm);
102
+ font-weight: var(--weight-semibold);
103
+ color: var(--text);
104
+ }
108
105
 
109
- .ui-pick:hover,
110
- .ui-pick.is-hover {
111
- border-color: var(--border-strong);
112
- }
106
+ .ui-pick__note {
107
+ font-size: var(--text-xs);
108
+ line-height: var(--leading-xs);
109
+ color: var(--text-muted);
110
+ }
113
111
 
114
- /* Selected: 2 px border. */
115
- .ui-pick.is-selected {
116
- border-width: 2px;
117
- border-color: var(--border-strong);
118
- background: var(--surface-accent);
119
- }
112
+ .ui-pick:hover,
113
+ .ui-pick.is-hover {
114
+ border-color: var(--border-strong);
115
+ }
120
116
 
121
- .ui-pick.is-selected .ui-pick__title {
122
- color: var(--surface-accent-fg, var(--text));
123
- }
117
+ /* Selected: 2 px border. */
118
+ .ui-pick.is-selected {
119
+ border-width: 2px;
120
+ border-color: var(--border-strong);
121
+ background: var(--surface-accent);
122
+ }
124
123
 
125
- /* The selected card sits on the sage accent surface, where --text-muted only
126
- reaches 3.6:1. The accent surface carries its own muted role for exactly
127
- this case. */
128
- .ui-pick.is-selected .ui-pick__note {
129
- color: var(--surface-accent-muted, var(--text));
130
- }
124
+ .ui-pick.is-selected .ui-pick__title {
125
+ color: var(--surface-accent-fg, var(--text));
126
+ }
131
127
 
132
- .ui-pick.is-disabled {
133
- opacity: 0.5;
134
- cursor: not-allowed;
135
- }
128
+ /* The selected card sits on the sage accent surface, where --text-muted only */
129
+ /* reaches 3.6:1. The accent surface carries its own muted role for exactly */
130
+ /* this case. */
131
+ .ui-pick.is-selected .ui-pick__note {
132
+ color: var(--surface-accent-muted, var(--text));
133
+ }
136
134
 
137
- @media (prefers-reduced-motion: reduce) {
138
- .ui-pick {
139
- transition: none;
135
+ .ui-pick.is-disabled {
136
+ opacity: 0.5;
137
+ cursor: not-allowed;
138
+ }
139
+
140
+ @media (prefers-reduced-motion: reduce) {
141
+ .ui-pick {
142
+ transition: none;
143
+ }
140
144
  }
141
145
  }
142
146
  </style>
@@ -41,16 +41,27 @@ const ribbonLabel: Record<NonNullable<Props["ribbon"]>, string> = {
41
41
  fav: "Favoriet",
42
42
  };
43
43
 
44
- const classes = ["ui-product", soldOut && "is-disabled", className].filter(Boolean).join(" ");
44
+ const classes = ["ui-product", soldOut && "is-disabled", className]
45
+ .filter(Boolean)
46
+ .join(" ");
45
47
  ---
46
48
 
47
49
  <a class={classes} href={href} {...rest}>
48
50
  {(ribbon || soldOut) && (
49
- <span class={`ui-ribbon ui-ribbon--${soldOut ? "sold-out" : ribbon}`} aria-hidden="true">
51
+ <span
52
+ class={`ui-ribbon ui-ribbon--${soldOut ? "sold-out" : ribbon}`}
53
+ aria-hidden="true"
54
+ >
50
55
  <i>{soldOut ? "Uitverkocht" : ribbonLabel[ribbon!]}</i>
51
56
  </span>
52
57
  )}
53
- <img class="ui-product__img" loading="lazy" decoding="async" src={img} alt={alt} />
58
+ <img
59
+ class="ui-product__img"
60
+ loading="lazy"
61
+ decoding="async"
62
+ src={img}
63
+ alt={alt}
64
+ />
54
65
  <span class="ui-product__body">
55
66
  <span class="ui-product__title">{title}</span>
56
67
  {meta && <span class="ui-product__meta">{meta}</span>}
@@ -63,133 +74,137 @@ const classes = ["ui-product", soldOut && "is-disabled", className].filter(Boole
63
74
  </a>
64
75
 
65
76
  <style>
66
- .ui-product {
67
- position: relative;
68
- display: flex;
69
- flex-direction: column;
70
- border: 1px solid var(--border);
71
- border-radius: var(--radius-lg);
72
- overflow: hidden;
73
- background: var(--surface);
74
- color: var(--text);
75
- text-decoration: none;
76
- transition: border-color var(--duration-fast) var(--ease-standard);
77
- }
77
+ @layer reset, tokens, base, components, motion, utilities;
78
78
 
79
- .ui-product:hover,
80
- .ui-product.is-hover {
81
- border-color: var(--border-strong);
82
- }
79
+ @layer components {
80
+ .ui-product {
81
+ position: relative;
82
+ display: flex;
83
+ flex-direction: column;
84
+ border: 1px solid var(--border);
85
+ border-radius: var(--radius-lg);
86
+ overflow: hidden;
87
+ background: var(--surface);
88
+ color: var(--text);
89
+ text-decoration: none;
90
+ transition: border-color var(--duration-fast) var(--ease-standard);
91
+ }
83
92
 
84
- .ui-product:focus-visible {
85
- outline: 2px solid var(--focus);
86
- outline-offset: 2px;
87
- box-shadow: 0 0 0 4px var(--focus-outer);
88
- }
93
+ .ui-product:hover,
94
+ .ui-product.is-hover {
95
+ border-color: var(--border-strong);
96
+ }
89
97
 
90
- .ui-product__img {
91
- display: block;
92
- width: 100%;
93
- aspect-ratio: 4 / 3;
94
- object-fit: cover;
95
- background: var(--surface-hover);
96
- }
98
+ .ui-product:focus-visible {
99
+ outline: 2px solid var(--focus);
100
+ outline-offset: 2px;
101
+ box-shadow: 0 0 0 4px var(--focus-outer);
102
+ }
97
103
 
98
- .ui-product__body {
99
- display: flex;
100
- flex-direction: column;
101
- gap: var(--space-1);
102
- padding: var(--space-3) var(--space-4) var(--space-4);
103
- }
104
+ .ui-product__img {
105
+ display: block;
106
+ width: 100%;
107
+ aspect-ratio: 4 / 3;
108
+ object-fit: cover;
109
+ background: var(--surface-hover);
110
+ }
104
111
 
105
- .ui-product__title {
106
- font-size: var(--text-base);
107
- font-weight: var(--weight-semibold);
108
- color: var(--text);
109
- }
112
+ .ui-product__body {
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: var(--space-1);
116
+ padding: var(--space-3) var(--space-4) var(--space-4);
117
+ }
110
118
 
111
- .ui-product__meta {
112
- font-size: var(--text-xs);
113
- color: var(--text-muted);
114
- }
119
+ .ui-product__title {
120
+ font-size: var(--text-base);
121
+ font-weight: var(--weight-semibold);
122
+ color: var(--text);
123
+ }
115
124
 
116
- .ui-product__price {
117
- display: flex;
118
- align-items: baseline;
119
- gap: var(--space-2);
120
- margin-top: var(--space-1);
121
- font-size: var(--text-lg);
122
- font-weight: var(--weight-semibold);
123
- font-variant-numeric: tabular-nums;
124
- color: var(--text);
125
- }
125
+ .ui-product__meta {
126
+ font-size: var(--text-xs);
127
+ color: var(--text-muted);
128
+ }
126
129
 
127
- .ui-product__price s {
128
- font-size: var(--text-sm);
129
- font-weight: var(--weight-regular);
130
- color: var(--text-muted);
131
- }
130
+ .ui-product__price {
131
+ display: flex;
132
+ align-items: baseline;
133
+ gap: var(--space-2);
134
+ margin-top: var(--space-1);
135
+ font-size: var(--text-lg);
136
+ font-weight: var(--weight-semibold);
137
+ font-variant-numeric: tabular-nums;
138
+ color: var(--text);
139
+ }
132
140
 
133
- /* Sold out: card stays readable and clickable. */
134
- .ui-product.is-disabled .ui-product__img {
135
- opacity: 0.7;
136
- }
137
- .ui-product.is-disabled .ui-product__price {
138
- color: var(--text-muted);
139
- font-weight: var(--weight-regular);
140
- }
141
+ .ui-product__price s {
142
+ font-size: var(--text-sm);
143
+ font-weight: var(--weight-regular);
144
+ color: var(--text-muted);
145
+ }
141
146
 
142
- /* Ribbon. One per card, decorative. 112 px box. */
143
- .ui-ribbon {
144
- position: absolute;
145
- top: 0;
146
- left: 0;
147
- width: 112px;
148
- height: 112px;
149
- overflow: hidden;
150
- pointer-events: none;
151
- z-index: 1;
152
- }
153
- .ui-ribbon i {
154
- position: absolute;
155
- top: 22px;
156
- left: -34px;
157
- width: 140px;
158
- transform: rotate(-45deg);
159
- text-align: center;
160
- font-size: var(--text-2xs);
161
- font-weight: var(--weight-bold);
162
- letter-spacing: var(--tracking-wider);
163
- text-transform: uppercase;
164
- font-style: normal;
165
- padding: var(--space-1) 0;
166
- color: var(--primary-fg);
167
- background: var(--primary);
168
- }
169
- .ui-ribbon--new i {
170
- background: var(--primary);
171
- color: var(--primary-fg);
172
- }
173
- .ui-ribbon--promo i {
174
- background: var(--accent);
175
- color: var(--accent-fg);
176
- }
177
- .ui-ribbon--last i {
178
- background: var(--warning);
179
- color: var(--primary-fg);
180
- }
181
- .ui-ribbon--fav i {
182
- background: var(--surface-accent);
183
- color: var(--surface-accent-fg);
184
- }
185
- .ui-ribbon--sold-out i {
186
- background: var(--danger-bg);
187
- color: var(--danger);
188
- }
147
+ /* Sold out: card stays readable and clickable. */
148
+ .ui-product.is-disabled .ui-product__img {
149
+ opacity: 0.7;
150
+ }
151
+ .ui-product.is-disabled .ui-product__price {
152
+ color: var(--text-muted);
153
+ font-weight: var(--weight-regular);
154
+ }
189
155
 
190
- @media (prefers-reduced-motion: reduce) {
191
- .ui-product {
192
- transition: none;
156
+ /* Ribbon. One per card, decorative. 112 px box. */
157
+ .ui-ribbon {
158
+ position: absolute;
159
+ top: 0;
160
+ left: 0;
161
+ width: 112px;
162
+ height: 112px;
163
+ overflow: hidden;
164
+ pointer-events: none;
165
+ z-index: 1;
166
+ }
167
+ .ui-ribbon i {
168
+ position: absolute;
169
+ top: 22px;
170
+ left: -34px;
171
+ width: 140px;
172
+ transform: rotate(-45deg);
173
+ text-align: center;
174
+ font-size: var(--text-2xs);
175
+ font-weight: var(--weight-bold);
176
+ letter-spacing: var(--tracking-wider);
177
+ text-transform: uppercase;
178
+ font-style: normal;
179
+ padding: var(--space-1) 0;
180
+ color: var(--primary-fg);
181
+ background: var(--primary);
182
+ }
183
+ .ui-ribbon--new i {
184
+ background: var(--primary);
185
+ color: var(--primary-fg);
186
+ }
187
+ .ui-ribbon--promo i {
188
+ background: var(--accent);
189
+ color: var(--accent-fg);
190
+ }
191
+ .ui-ribbon--last i {
192
+ background: var(--warning);
193
+ color: var(--primary-fg);
194
+ }
195
+ .ui-ribbon--fav i {
196
+ background: var(--surface-accent);
197
+ color: var(--surface-accent-fg);
198
+ }
199
+ .ui-ribbon--sold-out i {
200
+ background: var(--danger-bg);
201
+ color: var(--danger);
202
+ }
203
+
204
+ @media (prefers-reduced-motion: reduce) {
205
+ .ui-product {
206
+ transition: none;
207
+ }
193
208
  }
194
209
  }
195
210
  </style>