@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
@@ -19,46 +19,50 @@ const classes = ["ui-footer__col", className].filter(Boolean).join(" ");
19
19
  </div>
20
20
 
21
21
  <style>
22
- .ui-footer__col {
23
- display: flex;
24
- flex-direction: column;
25
- gap: var(--space-2-5, var(--space-2));
26
- }
22
+ @layer reset, tokens, base, components, motion, utilities;
27
23
 
28
- .ui-footer__title {
29
- font-family: var(--font-mono);
30
- font-size: var(--text-2xs);
31
- font-weight: var(--weight-semibold);
32
- text-transform: uppercase;
33
- letter-spacing: var(--tracking-widest);
34
- color: var(--sage-light);
35
- margin-bottom: var(--space-1);
36
- }
24
+ @layer components {
25
+ .ui-footer__col {
26
+ display: flex;
27
+ flex-direction: column;
28
+ gap: var(--space-2-5, var(--space-2));
29
+ }
37
30
 
38
- .ui-footer__col :global(a),
39
- .ui-footer__col :global(.ui-footer__note) {
40
- font-size: var(--text-sm);
41
- line-height: var(--leading-sm);
42
- color: var(--ivory-light);
43
- }
31
+ .ui-footer__title {
32
+ font-family: var(--font-mono);
33
+ font-size: var(--text-2xs);
34
+ font-weight: var(--weight-semibold);
35
+ text-transform: uppercase;
36
+ letter-spacing: var(--tracking-widest);
37
+ color: var(--sage-light);
38
+ margin-bottom: var(--space-1);
39
+ }
44
40
 
45
- .ui-footer__col :global(a) {
46
- text-decoration: underline;
47
- text-underline-offset: 2px;
48
- transition: color var(--duration-fast) var(--ease-standard);
49
- }
50
- .ui-footer__col :global(a:hover) {
51
- color: var(--ivory-xlight);
52
- }
53
- .ui-footer__col :global(a:focus-visible) {
54
- outline: 2px solid var(--ivory-xlight);
55
- outline-offset: 2px;
56
- border-radius: var(--radius-sm);
57
- }
41
+ .ui-footer__col :global(a),
42
+ .ui-footer__col :global(.ui-footer__note) {
43
+ font-size: var(--text-sm);
44
+ line-height: var(--leading-sm);
45
+ color: var(--ivory-light);
46
+ }
58
47
 
59
- @media (prefers-reduced-motion: reduce) {
60
48
  .ui-footer__col :global(a) {
61
- transition: none;
49
+ text-decoration: underline;
50
+ text-underline-offset: 2px;
51
+ transition: color var(--duration-fast) var(--ease-standard);
52
+ }
53
+ .ui-footer__col :global(a:hover) {
54
+ color: var(--ivory-xlight);
55
+ }
56
+ .ui-footer__col :global(a:focus-visible) {
57
+ outline: 2px solid var(--ivory-xlight);
58
+ outline-offset: 2px;
59
+ border-radius: var(--radius-sm);
60
+ }
61
+
62
+ @media (prefers-reduced-motion: reduce) {
63
+ .ui-footer__col :global(a) {
64
+ transition: none;
65
+ }
62
66
  }
63
67
  }
64
68
  </style>
@@ -13,16 +13,25 @@ interface Props {
13
13
  class?: string;
14
14
  }
15
15
 
16
- const { brandName = "KWTV", brandHref = "/", class: className, ...rest } =
17
- Astro.props;
16
+ const {
17
+ brandName = "KWTV",
18
+ brandHref = "/",
19
+ class: className,
20
+ ...rest
21
+ } = Astro.props;
18
22
 
19
23
  const classes = ["ui-header", className].filter(Boolean).join(" ");
20
24
  ---
21
25
 
22
26
  <header class={classes} {...rest}>
23
27
  <a class="ui-brand" href={brandHref}>
24
- <span class="ui-brand__mark"><slot name="logo" /></span>
25
- <span class="ui-brand__name">{brandName}<slot name="brand-suffix" /></span>
28
+ <span class="ui-brand__mark">
29
+ <slot name="logo" />
30
+ </span>
31
+ <span class="ui-brand__name">
32
+ {brandName}
33
+ <slot name="brand-suffix" />
34
+ </span>
26
35
  </a>
27
36
 
28
37
  <nav class="ui-nav">
@@ -35,82 +44,86 @@ const classes = ["ui-header", className].filter(Boolean).join(" ");
35
44
  </header>
36
45
 
37
46
  <style>
38
- .ui-header {
39
- display: flex;
40
- align-items: center;
41
- justify-content: space-between;
42
- gap: var(--space-6);
43
- min-height: 73px;
44
- padding: var(--space-5) var(--space-10);
45
- background: var(--bg);
46
- border-bottom: 1px solid var(--border);
47
- }
47
+ @layer reset, tokens, base, components, motion, utilities;
48
48
 
49
- .ui-brand {
50
- display: inline-flex;
51
- align-items: center;
52
- gap: var(--space-2-5, var(--space-2));
53
- text-decoration: none;
54
- color: var(--text);
55
- }
56
- .ui-brand:focus-visible {
57
- outline: 2px solid var(--focus);
58
- outline-offset: 2px;
59
- border-radius: var(--radius-sm);
60
- }
49
+ @layer components {
50
+ .ui-header {
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: space-between;
54
+ gap: var(--space-6);
55
+ min-height: 73px;
56
+ padding: var(--space-5) var(--space-10);
57
+ background: var(--bg);
58
+ border-bottom: 1px solid var(--border);
59
+ }
61
60
 
62
- .ui-brand__mark {
63
- display: inline-flex;
64
- align-items: center;
65
- justify-content: center;
66
- }
61
+ .ui-brand {
62
+ display: inline-flex;
63
+ align-items: center;
64
+ gap: var(--space-2-5, var(--space-2));
65
+ text-decoration: none;
66
+ color: var(--text);
67
+ }
68
+ .ui-brand:focus-visible {
69
+ outline: 2px solid var(--focus);
70
+ outline-offset: 2px;
71
+ border-radius: var(--radius-sm);
72
+ }
67
73
 
68
- .ui-brand__name {
69
- font-family: var(--font-sans);
70
- font-size: var(--text-lg);
71
- font-weight: var(--weight-bold);
72
- letter-spacing: var(--tracking-tight);
73
- }
74
- /* The shop wordmark: a lighter "shop" after the name. */
75
- .ui-brand__name :global(span) {
76
- font-weight: var(--weight-regular);
77
- color: var(--text-muted);
78
- }
74
+ .ui-brand__mark {
75
+ display: inline-flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ }
79
79
 
80
- .ui-nav {
81
- display: flex;
82
- align-items: center;
83
- gap: var(--space-6);
84
- }
80
+ .ui-brand__name {
81
+ font-family: var(--font-sans);
82
+ font-size: var(--text-lg);
83
+ font-weight: var(--weight-bold);
84
+ letter-spacing: var(--tracking-tight);
85
+ }
86
+ /* The shop wordmark: a lighter "shop" after the name. */
87
+ .ui-brand__name :global(span) {
88
+ font-weight: var(--weight-regular);
89
+ color: var(--text-muted);
90
+ }
85
91
 
86
- .ui-header__mobile {
87
- display: none;
88
- gap: var(--space-2);
89
- }
92
+ .ui-nav {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: var(--space-6);
96
+ }
90
97
 
91
- /* Pin the compact state in component explorers and other constrained shells. */
92
- .ui-header--mobile {
93
- padding-inline: var(--space-4);
94
- }
95
- .ui-header--mobile .ui-nav {
96
- display: none;
97
- }
98
- .ui-header--mobile .ui-header__mobile {
99
- display: flex;
100
- }
98
+ .ui-header__mobile {
99
+ display: none;
100
+ gap: var(--space-2);
101
+ }
101
102
 
102
- /* Below 900 px the links collapse. The consumer fills the mobile slot with
103
- the menu and cart buttons and opens the Drawer from there. */
104
- @media (max-width: 900px) {
105
- .ui-header,
103
+ /* Pin the compact state in component explorers and other constrained shells. */
106
104
  .ui-header--mobile {
107
105
  padding-inline: var(--space-4);
108
106
  }
109
- .ui-header:not(.ui-header--mobile) .ui-nav {
107
+ .ui-header--mobile .ui-nav {
110
108
  display: none;
111
109
  }
112
- .ui-header:not(.ui-header--mobile) .ui-header__mobile {
110
+ .ui-header--mobile .ui-header__mobile {
113
111
  display: flex;
114
112
  }
113
+
114
+ /* Below 900 px the links collapse. The consumer fills the mobile slot with */
115
+ /* the menu and cart buttons and opens the Drawer from there. */
116
+ @media (max-width: 900px) {
117
+ .ui-header,
118
+ .ui-header--mobile {
119
+ padding-inline: var(--space-4);
120
+ }
121
+ .ui-header:not(.ui-header--mobile) .ui-nav {
122
+ display: none;
123
+ }
124
+ .ui-header:not(.ui-header--mobile) .ui-header__mobile {
125
+ display: flex;
126
+ }
127
+ }
115
128
  }
116
129
  </style>
@@ -42,14 +42,16 @@ const paths: Record<Props["name"], string> = {
42
42
  "chevron-right": '<path d="M9 6l6 6-6 6"/>',
43
43
  "chevron-left": '<path d="M15 6l-6 6 6 6"/>',
44
44
  info: '<circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 8v.5"/>',
45
- "alert-circle": '<circle cx="12" cy="12" r="9"/><path d="M12 8v4.5M12 16v.5"/>',
45
+ "alert-circle":
46
+ '<circle cx="12" cy="12" r="9"/><path d="M12 8v4.5M12 16v.5"/>',
46
47
  "alert-triangle": '<path d="M12 4l8.5 15h-17L12 4zM12 10v4M12 16.5v.5"/>',
47
48
  "arrow-right": '<path d="M5 12h14M13 6l6 6-6 6"/>',
48
49
  "arrow-up-right": '<path d="M7 17 17 7M8 7h9v9"/>',
49
50
  "arrow-down": '<path d="M12 5v14M6 13l6 6 6-6"/>',
50
51
  instagram:
51
52
  '<rect x="4" y="4" width="16" height="16" rx="5"/><circle cx="12" cy="12" r="3.6"/><path d="M17 7.2v.01"/>',
52
- facebook: '<path d="M14.5 8.5h2M14.5 8.5c0-2 .8-3 2.5-3M14.5 8.5V19M11 12h6"/>',
53
+ facebook:
54
+ '<path d="M17 3.5h-2.5a4 4 0 0 0-4 4v3H8v3.5h2.5v6.5H14V14h2.6l.6-3.5H14v-2.2a1 1 0 0 1 1-1h2z"/>',
53
55
  youtube:
54
56
  '<rect x="3.5" y="6.5" width="17" height="11" rx="3.2"/><path d="M11 10.2l3.4 1.8-3.4 1.8z"/>',
55
57
  };
@@ -41,77 +41,83 @@ const classes = ["ui-iconbtn", className].filter(Boolean).join(" ");
41
41
  >
42
42
  {icon ? <Icon name={icon as any} size={20} stroke={1.7} /> : <slot />}
43
43
  {count !== undefined && (
44
- <span class="ui-iconbtn__count" aria-hidden="true">{count}</span>
44
+ <span class="ui-iconbtn__count" aria-hidden="true">
45
+ {count}
46
+ </span>
45
47
  )}
46
48
  </button>
47
49
 
48
50
  <style>
49
- .ui-iconbtn {
50
- position: relative;
51
- display: inline-flex;
52
- align-items: center;
53
- justify-content: center;
54
- width: 44px;
55
- height: 44px;
56
- padding: 0;
57
- background: transparent;
58
- border: 1px solid transparent;
59
- border-radius: var(--radius-md);
60
- color: var(--text);
61
- cursor: pointer;
62
- transition:
63
- background-color var(--duration-fast) var(--ease-standard),
64
- border-color var(--duration-fast) var(--ease-standard);
65
- }
51
+ @layer reset, tokens, base, components, motion, utilities;
66
52
 
67
- .ui-iconbtn:hover,
68
- .ui-iconbtn.is-hover {
69
- background: var(--surface-hover);
70
- border-color: var(--border-strong);
71
- }
53
+ @layer components {
54
+ .ui-iconbtn {
55
+ position: relative;
56
+ display: inline-flex;
57
+ align-items: center;
58
+ justify-content: center;
59
+ width: 44px;
60
+ height: 44px;
61
+ padding: 0;
62
+ background: transparent;
63
+ border: 1px solid transparent;
64
+ border-radius: var(--radius-md);
65
+ color: var(--text);
66
+ cursor: pointer;
67
+ transition:
68
+ background-color var(--duration-fast) var(--ease-standard),
69
+ border-color var(--duration-fast) var(--ease-standard);
70
+ }
72
71
 
73
- .ui-iconbtn:focus-visible,
74
- .ui-iconbtn.is-focus {
75
- outline: 2px solid var(--focus);
76
- outline-offset: 2px;
77
- box-shadow: 0 0 0 4px var(--focus-outer);
78
- }
72
+ .ui-iconbtn:hover,
73
+ .ui-iconbtn.is-hover {
74
+ background: var(--surface-hover);
75
+ border-color: var(--border-strong);
76
+ }
79
77
 
80
- .ui-iconbtn:active,
81
- .ui-iconbtn.is-active {
82
- background: var(--surface-hover);
83
- }
78
+ .ui-iconbtn:focus-visible,
79
+ .ui-iconbtn.is-focus {
80
+ outline: 2px solid var(--focus);
81
+ outline-offset: 2px;
82
+ box-shadow: 0 0 0 4px var(--focus-outer);
83
+ }
84
84
 
85
- .ui-iconbtn:disabled,
86
- .ui-iconbtn.is-disabled {
87
- opacity: 0.5;
88
- cursor: not-allowed;
89
- pointer-events: none;
90
- }
85
+ .ui-iconbtn:active,
86
+ .ui-iconbtn.is-active {
87
+ background: var(--surface-hover);
88
+ }
91
89
 
92
- /* Count pill: 19 px, accent fill, top-right. */
93
- .ui-iconbtn__count {
94
- position: absolute;
95
- top: 3px;
96
- right: 3px;
97
- display: inline-flex;
98
- align-items: center;
99
- justify-content: center;
100
- min-width: 19px;
101
- height: 19px;
102
- padding: 0 var(--space-1);
103
- background: var(--accent);
104
- color: var(--accent-fg);
105
- border-radius: var(--radius-full);
106
- font-family: var(--font-sans);
107
- font-size: var(--text-2xs);
108
- font-weight: var(--weight-semibold);
109
- line-height: 1;
110
- }
90
+ .ui-iconbtn:disabled,
91
+ .ui-iconbtn.is-disabled {
92
+ opacity: 0.5;
93
+ cursor: not-allowed;
94
+ pointer-events: none;
95
+ }
111
96
 
112
- @media (prefers-reduced-motion: reduce) {
113
- .ui-iconbtn {
114
- transition: none;
97
+ /* Count pill: 19 px, accent fill, top-right. */
98
+ .ui-iconbtn__count {
99
+ position: absolute;
100
+ top: 3px;
101
+ right: 3px;
102
+ display: inline-flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ min-width: 19px;
106
+ height: 19px;
107
+ padding: 0 var(--space-1);
108
+ background: var(--accent);
109
+ color: var(--accent-fg);
110
+ border-radius: var(--radius-full);
111
+ font-family: var(--font-sans);
112
+ font-size: var(--text-2xs);
113
+ font-weight: var(--weight-semibold);
114
+ line-height: 1;
115
+ }
116
+
117
+ @media (prefers-reduced-motion: reduce) {
118
+ .ui-iconbtn {
119
+ transition: none;
120
+ }
115
121
  }
116
122
  }
117
123
  </style>
@@ -21,52 +21,54 @@ const { items, divider = "✦", class: className } = Astro.props;
21
21
 
22
22
  <div class:list={["ui-marquee", className]} aria-hidden="true">
23
23
  <div class="ui-marquee__track">
24
- {
25
- [0, 1].map(() => (
26
- <div class="ui-marquee__group">
27
- {items.map((item) => (
28
- <>
29
- <span>{item}</span>
30
- <span class="ui-marquee__div">{divider}</span>
31
- </>
32
- ))}
33
- </div>
34
- ))
35
- }
24
+ {[0, 1].map(() => (
25
+ <div class="ui-marquee__group">
26
+ {items.map((item) => (
27
+ <>
28
+ <span>{item}</span>
29
+ <span class="ui-marquee__div">{divider}</span>
30
+ </>
31
+ ))}
32
+ </div>
33
+ ))}
36
34
  </div>
37
35
  </div>
38
36
 
39
37
  <style>
40
- .ui-marquee {
41
- background: var(--surface-banner);
42
- color: var(--surface-inverse-fg);
43
- overflow: hidden;
44
- padding-block: var(--space-3);
45
- border-block: 1px solid var(--surface-inverse-border);
46
- transform: rotate(-1.2deg) scale(1.02);
47
- }
48
- .ui-marquee__track {
49
- display: flex;
50
- width: max-content;
51
- animation: pl-marquee 22s linear infinite;
52
- font-size: var(--text-sm);
53
- font-weight: var(--weight-semibold);
54
- letter-spacing: var(--tracking-wider);
55
- text-transform: uppercase;
56
- }
57
- .ui-marquee__group {
58
- display: flex;
59
- align-items: center;
60
- gap: var(--space-6);
61
- padding-right: var(--space-6);
62
- white-space: nowrap;
63
- }
64
- .ui-marquee__div {
65
- color: var(--sage-light);
66
- }
67
- @media (prefers-reduced-motion: reduce) {
38
+ @layer reset, tokens, base, components, motion, utilities;
39
+
40
+ @layer components {
41
+ .ui-marquee {
42
+ background: var(--surface-banner);
43
+ color: var(--surface-inverse-fg);
44
+ overflow: hidden;
45
+ padding-block: var(--space-3);
46
+ border-block: 1px solid var(--surface-inverse-border);
47
+ transform: rotate(-1.2deg) scale(1.02);
48
+ }
68
49
  .ui-marquee__track {
69
- animation: none;
50
+ display: flex;
51
+ width: max-content;
52
+ animation: pl-marquee 22s linear infinite;
53
+ font-size: var(--text-sm);
54
+ font-weight: var(--weight-semibold);
55
+ letter-spacing: var(--tracking-wider);
56
+ text-transform: uppercase;
57
+ }
58
+ .ui-marquee__group {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: var(--space-6);
62
+ padding-right: var(--space-6);
63
+ white-space: nowrap;
64
+ }
65
+ .ui-marquee__div {
66
+ color: var(--sage-light);
67
+ }
68
+ @media (prefers-reduced-motion: reduce) {
69
+ .ui-marquee__track {
70
+ animation: none;
71
+ }
70
72
  }
71
73
  }
72
74
  </style>