@kwtv/components 0.3.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 (49) hide show
  1. package/README.md +35 -0
  2. package/package.json +2 -2
  3. package/src/astro/Accordion.astro +10 -6
  4. package/src/astro/AccordionItem.astro +47 -43
  5. package/src/astro/ActiveFilterTags.astro +29 -25
  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 +33 -29
  10. package/src/astro/Button.astro +132 -128
  11. package/src/astro/Card.astro +64 -60
  12. package/src/astro/CartLine.astro +59 -55
  13. package/src/astro/Checkbox.astro +36 -32
  14. package/src/astro/Chip.astro +68 -64
  15. package/src/astro/DotGrid.astro +50 -46
  16. package/src/astro/EmptyState.astro +26 -22
  17. package/src/astro/FilterGroup.astro +29 -25
  18. package/src/astro/Filters.astro +50 -46
  19. package/src/astro/Footer.astro +179 -175
  20. package/src/astro/FooterColumn.astro +39 -35
  21. package/src/astro/Header.astro +67 -63
  22. package/src/astro/Icon.astro +1 -1
  23. package/src/astro/IconButton.astro +64 -60
  24. package/src/astro/Marquee.astro +33 -29
  25. package/src/astro/NavDropdown.astro +120 -116
  26. package/src/astro/NavLink.astro +59 -55
  27. package/src/astro/Newsletter.astro +58 -54
  28. package/src/astro/Pager.astro +43 -39
  29. package/src/astro/PickCard.astro +72 -68
  30. package/src/astro/ProductCard.astro +120 -116
  31. package/src/astro/Radio.astro +36 -32
  32. package/src/astro/Skeleton.astro +43 -39
  33. package/src/astro/Social.astro +26 -22
  34. package/src/astro/StepIndicator.astro +55 -51
  35. package/src/astro/Sticker.astro +50 -46
  36. package/src/astro/Swatch.astro +44 -40
  37. package/src/astro/Table.astro +58 -54
  38. package/src/astro/TableCard.astro +39 -35
  39. package/src/astro/TextField.astro +92 -88
  40. package/src/astro/Totals.astro +39 -35
  41. package/src/react/Drawer.css +60 -56
  42. package/src/react/Modal.css +97 -93
  43. package/src/react/NewsCarousel.css +108 -104
  44. package/src/react/QuantityStepper.css +84 -80
  45. package/src/react/Select.css +159 -155
  46. package/src/react/Toast.css +128 -124
  47. package/src/styles/base.css +30 -26
  48. package/src/styles/controls.css +183 -179
  49. package/src/styles/motion.css +203 -199
package/README.md CHANGED
@@ -71,6 +71,41 @@ import "@kwtv/components/styles/base.css";
71
71
  import "@kwtv/components/styles/controls.css";
72
72
  ```
73
73
 
74
+ ## Cascade layers
75
+
76
+ Every stylesheet in this package and in `@kwtv/tokens` sits in a cascade
77
+ layer, in one fixed order:
78
+
79
+ ```css
80
+ @layer reset, tokens, base, components, motion, utilities;
81
+ ```
82
+
83
+ | Layer | Holds |
84
+ | ------------ | --------------------------------------------------------------- |
85
+ | `reset` | Empty in the packages. The app's element defaults go here. |
86
+ | `tokens` | The token custom properties, in `palette`, `theme` and `scale`. |
87
+ | `base` | `base.css`: focus ring, touch targets, screen-reader helper. |
88
+ | `components` | Astro component styles, React island CSS and `controls.css`. |
89
+ | `motion` | `motion.css`. Above components, because it adjusts their parts. |
90
+ | `utilities` | `@kwtv/tokens/utilities.css`. |
91
+
92
+ Every package stylesheet opens with that order statement. The first one to
93
+ load fixes the order, so it holds whatever order the bundler emits files in.
94
+
95
+ In the app:
96
+
97
+ - Put element defaults (`*`, `html`, `body`, `a`, `button`) in
98
+ `@layer reset { }`. Left unlayered, a bare `a { color }` beats the footer
99
+ and button link colours.
100
+ - Leave everything else unlayered. Unlayered CSS beats every layer, so a
101
+ single class overrides any component rule. Do not raise specificity with
102
+ ids or repeated classes.
103
+ - `!important` flips inside layers: an important declaration in a lower layer
104
+ wins. The package uses it only to stop animation under reduced motion.
105
+
106
+ `npm run layers:check` fails when a package stylesheet is missing the order
107
+ statement or has a rule outside its layer.
108
+
74
109
  ## Conventions
75
110
 
76
111
  - Use the `.astro`, `.tsx` and `.css` extensions shown in these examples.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kwtv/components",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "description": "KWTV UI components for Astro, with React islands. Built on the KWTV design tokens.",
@@ -34,6 +34,6 @@
34
34
  }
35
35
  },
36
36
  "dependencies": {
37
- "@kwtv/tokens": "^0.3.0"
37
+ "@kwtv/tokens": "^0.4.0"
38
38
  }
39
39
  }
@@ -15,11 +15,15 @@ const { class: className, ...rest } = Astro.props;
15
15
  </div>
16
16
 
17
17
  <style>
18
- .ui-accordion {
19
- display: flex;
20
- flex-direction: column;
21
- border: 1px solid var(--border);
22
- border-radius: var(--radius-lg);
23
- overflow: hidden;
18
+ @layer reset, tokens, base, components, motion, utilities;
19
+
20
+ @layer components {
21
+ .ui-accordion {
22
+ display: flex;
23
+ flex-direction: column;
24
+ border: 1px solid var(--border);
25
+ border-radius: var(--radius-lg);
26
+ overflow: hidden;
27
+ }
24
28
  }
25
29
  </style>
@@ -31,53 +31,57 @@ const { summary, open = false, class: className, ...rest } = Astro.props;
31
31
  </details>
32
32
 
33
33
  <style>
34
- .ui-accordion__item + .ui-accordion__item {
35
- border-top: 1px solid var(--border);
36
- }
37
-
38
- .ui-accordion__sum {
39
- display: flex;
40
- align-items: center;
41
- justify-content: space-between;
42
- gap: var(--space-3);
43
- min-height: 44px;
44
- padding: var(--space-2) var(--space-4);
45
- font-size: var(--text-sm);
46
- font-weight: var(--weight-medium);
47
- color: var(--text);
48
- cursor: pointer;
49
- list-style: none;
50
- }
51
- .ui-accordion__sum::-webkit-details-marker {
52
- display: none;
53
- }
54
- .ui-accordion__sum:hover {
55
- background: var(--surface-hover);
56
- }
57
- .ui-accordion__sum:focus-visible {
58
- outline: 2px solid var(--focus);
59
- outline-offset: -2px;
60
- }
34
+ @layer reset, tokens, base, components, motion, utilities;
61
35
 
62
- .ui-accordion__chev {
63
- display: inline-flex;
64
- color: var(--text-muted);
65
- transition: transform var(--duration-fast) var(--ease-standard);
66
- }
67
- .ui-accordion__item[open] .ui-accordion__chev {
68
- transform: rotate(180deg);
69
- }
36
+ @layer components {
37
+ .ui-accordion__item + .ui-accordion__item {
38
+ border-top: 1px solid var(--border);
39
+ }
70
40
 
71
- .ui-accordion__body {
72
- padding: 0 var(--space-4) var(--space-4);
73
- font-size: var(--text-sm);
74
- line-height: var(--leading-sm);
75
- color: var(--text-muted);
76
- }
41
+ .ui-accordion__sum {
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: space-between;
45
+ gap: var(--space-3);
46
+ min-height: 44px;
47
+ padding: var(--space-2) var(--space-4);
48
+ font-size: var(--text-sm);
49
+ font-weight: var(--weight-medium);
50
+ color: var(--text);
51
+ cursor: pointer;
52
+ list-style: none;
53
+ }
54
+ .ui-accordion__sum::-webkit-details-marker {
55
+ display: none;
56
+ }
57
+ .ui-accordion__sum:hover {
58
+ background: var(--surface-hover);
59
+ }
60
+ .ui-accordion__sum:focus-visible {
61
+ outline: 2px solid var(--focus);
62
+ outline-offset: -2px;
63
+ }
77
64
 
78
- @media (prefers-reduced-motion: reduce) {
79
65
  .ui-accordion__chev {
80
- transition: none;
66
+ display: inline-flex;
67
+ color: var(--text-muted);
68
+ transition: transform var(--duration-fast) var(--ease-standard);
69
+ }
70
+ .ui-accordion__item[open] .ui-accordion__chev {
71
+ transform: rotate(180deg);
72
+ }
73
+
74
+ .ui-accordion__body {
75
+ padding: 0 var(--space-4) var(--space-4);
76
+ font-size: var(--text-sm);
77
+ line-height: var(--leading-sm);
78
+ color: var(--text-muted);
79
+ }
80
+
81
+ @media (prefers-reduced-motion: reduce) {
82
+ .ui-accordion__chev {
83
+ transition: none;
84
+ }
81
85
  }
82
86
  }
83
87
  </style>
@@ -44,31 +44,35 @@ const classes = ["ui-filtertags", className].filter(Boolean).join(" ");
44
44
  </div>
45
45
 
46
46
  <style>
47
- .ui-filtertags {
48
- display: flex;
49
- flex-wrap: wrap;
50
- align-items: center;
51
- gap: var(--space-2);
52
- }
47
+ @layer reset, tokens, base, components, motion, utilities;
53
48
 
54
- .ui-filtertags__clear {
55
- padding: var(--space-1-5) var(--space-2);
56
- background: transparent;
57
- border: 0;
58
- border-radius: var(--radius-sm);
59
- font-family: var(--font-sans);
60
- font-size: var(--text-xs);
61
- font-weight: var(--weight-semibold);
62
- color: var(--link);
63
- cursor: pointer;
64
- }
65
- .ui-filtertags__clear:hover {
66
- background: var(--surface-hover);
67
- color: var(--link-hover);
68
- }
69
- .ui-filtertags__clear:focus-visible {
70
- outline: 2px solid var(--focus);
71
- outline-offset: 2px;
72
- box-shadow: 0 0 0 4px var(--focus-outer);
49
+ @layer components {
50
+ .ui-filtertags {
51
+ display: flex;
52
+ flex-wrap: wrap;
53
+ align-items: center;
54
+ gap: var(--space-2);
55
+ }
56
+
57
+ .ui-filtertags__clear {
58
+ padding: var(--space-1-5) var(--space-2);
59
+ background: transparent;
60
+ border: 0;
61
+ border-radius: var(--radius-sm);
62
+ font-family: var(--font-sans);
63
+ font-size: var(--text-xs);
64
+ font-weight: var(--weight-semibold);
65
+ color: var(--link);
66
+ cursor: pointer;
67
+ }
68
+ .ui-filtertags__clear:hover {
69
+ background: var(--surface-hover);
70
+ color: var(--link-hover);
71
+ }
72
+ .ui-filtertags__clear:focus-visible {
73
+ outline: 2px solid var(--focus);
74
+ outline-offset: 2px;
75
+ box-shadow: 0 0 0 4px var(--focus-outer);
76
+ }
73
77
  }
74
78
  </style>
@@ -41,53 +41,57 @@ const classes = ["ui-alert", `ui-alert--${tone}`, className]
41
41
  </div>
42
42
 
43
43
  <style>
44
- .ui-alert {
45
- display: flex;
46
- align-items: flex-start;
47
- gap: var(--space-2-5, 0.625rem);
48
- padding: var(--space-3) var(--space-4);
49
- border-left: 3px solid currentColor;
50
- border-radius: var(--radius-md);
51
- font-family: var(--font-sans);
52
- font-size: var(--text-sm);
53
- line-height: var(--leading-sm);
54
- }
44
+ @layer reset, tokens, base, components, motion, utilities;
55
45
 
56
- .ui-alert__icon {
57
- display: inline-flex;
58
- flex-shrink: 0;
59
- margin-top: 1px;
60
- }
46
+ @layer components {
47
+ .ui-alert {
48
+ display: flex;
49
+ align-items: flex-start;
50
+ gap: var(--space-2-5, 0.625rem);
51
+ padding: var(--space-3) var(--space-4);
52
+ border-left: 3px solid currentColor;
53
+ border-radius: var(--radius-md);
54
+ font-family: var(--font-sans);
55
+ font-size: var(--text-sm);
56
+ line-height: var(--leading-sm);
57
+ }
61
58
 
62
- /* Body keeps full strength; the left rule carries the tone. */
63
- .ui-alert__body {
64
- color: var(--text);
65
- display: flex;
66
- flex-direction: column;
67
- gap: var(--space-1);
68
- }
59
+ .ui-alert__icon {
60
+ display: inline-flex;
61
+ flex-shrink: 0;
62
+ margin-top: 1px;
63
+ }
69
64
 
70
- .ui-alert__title {
71
- font-weight: var(--weight-semibold);
72
- }
65
+ /* Body keeps full strength; the left rule carries the tone. */
66
+ .ui-alert__body {
67
+ color: var(--text);
68
+ display: flex;
69
+ flex-direction: column;
70
+ gap: var(--space-1);
71
+ }
73
72
 
74
- .ui-alert--info {
75
- background: var(--surface-accent);
76
- color: var(--surface-accent-fg, var(--primary));
77
- }
73
+ .ui-alert__title {
74
+ font-weight: var(--weight-semibold);
75
+ }
78
76
 
79
- .ui-alert--success {
80
- background: var(--success-bg);
81
- color: var(--success);
82
- }
77
+ .ui-alert--info {
78
+ background: var(--surface-accent);
79
+ color: var(--surface-accent-fg, var(--primary));
80
+ }
83
81
 
84
- .ui-alert--warning {
85
- background: var(--warning-bg);
86
- color: var(--warning);
87
- }
82
+ .ui-alert--success {
83
+ background: var(--success-bg);
84
+ color: var(--success);
85
+ }
86
+
87
+ .ui-alert--warning {
88
+ background: var(--warning-bg);
89
+ color: var(--warning);
90
+ }
88
91
 
89
- .ui-alert--danger {
90
- background: var(--danger-bg);
91
- color: var(--danger);
92
+ .ui-alert--danger {
93
+ background: var(--danger-bg);
94
+ color: var(--danger);
95
+ }
92
96
  }
93
97
  </style>
@@ -58,81 +58,85 @@ const classes = [
58
58
  </span>
59
59
 
60
60
  <style>
61
- .ui-badge {
62
- display: inline-flex;
63
- align-items: center;
64
- gap: var(--space-1-5);
65
- min-height: 24px;
66
- padding: 0 var(--space-2);
67
- border: 1px solid transparent;
68
- border-radius: var(--radius-full);
69
- font-family: var(--font-sans);
70
- font-size: var(--text-xs);
71
- font-weight: var(--weight-medium);
72
- line-height: 1;
73
- white-space: nowrap;
74
- }
75
-
76
- .ui-badge__icon {
77
- display: inline-flex;
78
- flex-shrink: 0;
79
- }
80
-
81
- .ui-badge--neutral {
82
- background: var(--surface-accent);
83
- color: var(--surface-accent-fg, var(--text));
84
- }
85
-
86
- .ui-badge--success {
87
- background: var(--success-bg);
88
- color: var(--success);
89
- }
90
-
91
- .ui-badge--warning {
92
- background: var(--warning-bg);
93
- color: var(--warning);
94
- }
95
-
96
- .ui-badge--danger {
97
- background: var(--danger-bg);
98
- color: var(--danger);
99
- }
100
-
101
- /* Outline keeps the strong border and full-strength text, so it reads
102
- without relying on a fill. */
103
- .ui-badge--outline {
104
- background: transparent;
105
- border: 1px solid var(--border-strong);
106
- color: var(--text);
107
- }
108
-
109
- .ui-badge--removable {
110
- padding-right: var(--space-1);
111
- }
112
-
113
- /* Small × target grows with padding plus an equal negative margin, so the
114
- hit area grows without moving the layout. */
115
- .ui-badge__x {
116
- display: inline-flex;
117
- align-items: center;
118
- justify-content: center;
119
- padding: var(--space-2);
120
- margin: calc(-1 * var(--space-2));
121
- margin-left: 0;
122
- background: transparent;
123
- border: 0;
124
- border-radius: var(--radius-sm);
125
- color: inherit;
126
- cursor: pointer;
127
- }
128
-
129
- .ui-badge__x:hover {
130
- color: var(--danger);
131
- }
132
-
133
- .ui-badge__x:focus-visible {
134
- outline: 2px solid var(--focus);
135
- outline-offset: 2px;
136
- box-shadow: 0 0 0 4px var(--focus-outer);
61
+ @layer reset, tokens, base, components, motion, utilities;
62
+
63
+ @layer components {
64
+ .ui-badge {
65
+ display: inline-flex;
66
+ align-items: center;
67
+ gap: var(--space-1-5);
68
+ min-height: 24px;
69
+ padding: 0 var(--space-2);
70
+ border: 1px solid transparent;
71
+ border-radius: var(--radius-full);
72
+ font-family: var(--font-sans);
73
+ font-size: var(--text-xs);
74
+ font-weight: var(--weight-medium);
75
+ line-height: 1;
76
+ white-space: nowrap;
77
+ }
78
+
79
+ .ui-badge__icon {
80
+ display: inline-flex;
81
+ flex-shrink: 0;
82
+ }
83
+
84
+ .ui-badge--neutral {
85
+ background: var(--surface-accent);
86
+ color: var(--surface-accent-fg, var(--text));
87
+ }
88
+
89
+ .ui-badge--success {
90
+ background: var(--success-bg);
91
+ color: var(--success);
92
+ }
93
+
94
+ .ui-badge--warning {
95
+ background: var(--warning-bg);
96
+ color: var(--warning);
97
+ }
98
+
99
+ .ui-badge--danger {
100
+ background: var(--danger-bg);
101
+ color: var(--danger);
102
+ }
103
+
104
+ /* Outline keeps the strong border and full-strength text, so it reads */
105
+ /* without relying on a fill. */
106
+ .ui-badge--outline {
107
+ background: transparent;
108
+ border: 1px solid var(--border-strong);
109
+ color: var(--text);
110
+ }
111
+
112
+ .ui-badge--removable {
113
+ padding-right: var(--space-1);
114
+ }
115
+
116
+ /* Small × target grows with padding plus an equal negative margin, so the */
117
+ /* hit area grows without moving the layout. */
118
+ .ui-badge__x {
119
+ display: inline-flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ padding: var(--space-2);
123
+ margin: calc(-1 * var(--space-2));
124
+ margin-left: 0;
125
+ background: transparent;
126
+ border: 0;
127
+ border-radius: var(--radius-sm);
128
+ color: inherit;
129
+ cursor: pointer;
130
+ }
131
+
132
+ .ui-badge__x:hover {
133
+ color: var(--danger);
134
+ }
135
+
136
+ .ui-badge__x:focus-visible {
137
+ outline: 2px solid var(--focus);
138
+ outline-offset: 2px;
139
+ box-shadow: 0 0 0 4px var(--focus-outer);
140
+ }
137
141
  }
138
142
  </style>