@wovenui/components 0.0.0-stage → 1.0.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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -2
  3. package/dist/components.json +4204 -0
  4. package/dist/llms-full.txt +2839 -0
  5. package/dist/wovenui-kit.css +5002 -0
  6. package/dist/wovenui-kit.css.map +1 -0
  7. package/dist/wovenui-kit.iife.js +532 -0
  8. package/dist/wovenui-kit.js +529 -0
  9. package/dist/wovenui-kit.min.css +2 -0
  10. package/dist/wovenui-kit.min.css.map +1 -0
  11. package/package.json +268 -4
  12. package/src/_close.scss +45 -0
  13. package/src/_helpers.scss +11 -0
  14. package/src/_kit.scss +124 -0
  15. package/src/_shared.scss +10 -0
  16. package/src/components/_accordion.scss +132 -0
  17. package/src/components/_alert.scss +161 -0
  18. package/src/components/_autocomplete.scss +129 -0
  19. package/src/components/_avatar-group.scss +80 -0
  20. package/src/components/_avatar.scss +134 -0
  21. package/src/components/_badge.scss +91 -0
  22. package/src/components/_breadcrumb.scss +115 -0
  23. package/src/components/_btn-group.scss +113 -0
  24. package/src/components/_button.scss +301 -0
  25. package/src/components/_card.scss +120 -0
  26. package/src/components/_checkbox.scss +168 -0
  27. package/src/components/_code-block.scss +78 -0
  28. package/src/components/_confirm-modal.scss +75 -0
  29. package/src/components/_data-table.scss +79 -0
  30. package/src/components/_desc-list.scss +180 -0
  31. package/src/components/_divider.scss +66 -0
  32. package/src/components/_dot.scss +115 -0
  33. package/src/components/_drawer.scss +200 -0
  34. package/src/components/_dropdown.scss +182 -0
  35. package/src/components/_empty-state.scss +146 -0
  36. package/src/components/_field-group.scss +68 -0
  37. package/src/components/_field-message.scss +75 -0
  38. package/src/components/_field.scss +127 -0
  39. package/src/components/_form-actions.scss +58 -0
  40. package/src/components/_info-row.scss +69 -0
  41. package/src/components/_input-group.scss +87 -0
  42. package/src/components/_input.scss +278 -0
  43. package/src/components/_list.scss +76 -0
  44. package/src/components/_modal.scss +168 -0
  45. package/src/components/_page-header.scss +85 -0
  46. package/src/components/_pagination.scss +138 -0
  47. package/src/components/_panel.scss +69 -0
  48. package/src/components/_popover.scss +154 -0
  49. package/src/components/_progress-circle.scss +123 -0
  50. package/src/components/_progress.scss +122 -0
  51. package/src/components/_radio.scss +162 -0
  52. package/src/components/_segmented.scss +183 -0
  53. package/src/components/_select.scss +154 -0
  54. package/src/components/_sidebar.scss +300 -0
  55. package/src/components/_skeleton.scss +90 -0
  56. package/src/components/_spinner.scss +84 -0
  57. package/src/components/_stat.scss +92 -0
  58. package/src/components/_table.scss +460 -0
  59. package/src/components/_tabs.scss +327 -0
  60. package/src/components/_tag.scss +143 -0
  61. package/src/components/_text.scss +42 -0
  62. package/src/components/_textarea.scss +57 -0
  63. package/src/components/_timeline.scss +196 -0
  64. package/src/components/_title.scss +41 -0
  65. package/src/components/_toast.scss +221 -0
  66. package/src/components/_toggle.scss +171 -0
  67. package/src/components/_toolbar.scss +74 -0
  68. package/src/components/_topbar.scss +204 -0
  69. package/src/entries/accordion.scss +4 -0
  70. package/src/entries/alert.scss +4 -0
  71. package/src/entries/autocomplete.scss +4 -0
  72. package/src/entries/avatar-group.scss +4 -0
  73. package/src/entries/avatar.scss +4 -0
  74. package/src/entries/badge.scss +4 -0
  75. package/src/entries/breadcrumb.scss +4 -0
  76. package/src/entries/btn-group.scss +4 -0
  77. package/src/entries/button.scss +4 -0
  78. package/src/entries/card.scss +4 -0
  79. package/src/entries/checkbox.scss +4 -0
  80. package/src/entries/code-block.scss +4 -0
  81. package/src/entries/confirm-modal.scss +4 -0
  82. package/src/entries/data-table.scss +4 -0
  83. package/src/entries/desc-list.scss +4 -0
  84. package/src/entries/divider.scss +4 -0
  85. package/src/entries/dot.scss +4 -0
  86. package/src/entries/drawer.scss +4 -0
  87. package/src/entries/dropdown.scss +4 -0
  88. package/src/entries/empty-state.scss +4 -0
  89. package/src/entries/field-group.scss +4 -0
  90. package/src/entries/field-message.scss +4 -0
  91. package/src/entries/field.scss +4 -0
  92. package/src/entries/form-actions.scss +4 -0
  93. package/src/entries/info-row.scss +4 -0
  94. package/src/entries/input-group.scss +4 -0
  95. package/src/entries/input.scss +4 -0
  96. package/src/entries/list.scss +4 -0
  97. package/src/entries/modal.scss +4 -0
  98. package/src/entries/page-header.scss +4 -0
  99. package/src/entries/pagination.scss +4 -0
  100. package/src/entries/panel.scss +4 -0
  101. package/src/entries/popover.scss +4 -0
  102. package/src/entries/progress-circle.scss +4 -0
  103. package/src/entries/progress.scss +4 -0
  104. package/src/entries/radio.scss +4 -0
  105. package/src/entries/segmented.scss +4 -0
  106. package/src/entries/select.scss +4 -0
  107. package/src/entries/sidebar.scss +4 -0
  108. package/src/entries/skeleton.scss +4 -0
  109. package/src/entries/spinner.scss +4 -0
  110. package/src/entries/stat.scss +4 -0
  111. package/src/entries/table.scss +4 -0
  112. package/src/entries/tabs.scss +4 -0
  113. package/src/entries/tag.scss +4 -0
  114. package/src/entries/text.scss +4 -0
  115. package/src/entries/textarea.scss +4 -0
  116. package/src/entries/timeline.scss +4 -0
  117. package/src/entries/title.scss +4 -0
  118. package/src/entries/toast.scss +4 -0
  119. package/src/entries/toggle.scss +4 -0
  120. package/src/entries/toolbar.scss +4 -0
  121. package/src/entries/topbar.scss +4 -0
  122. package/src/index.scss +7 -0
  123. package/src/js/autocomplete.js +86 -0
  124. package/src/js/data-table.js +141 -0
  125. package/src/js/index.js +34 -0
  126. package/src/js/input-clear.js +25 -0
  127. package/src/js/package.json +3 -0
  128. package/src/js/tabs.js +73 -0
  129. package/src/js/toast.js +161 -0
  130. package/src/js/util.js +21 -0
@@ -0,0 +1,196 @@
1
+ ////
2
+ //// @component wu-timeline
3
+ //// Ordered list of events with markers and a rail, role colors and a progress mode.
4
+ ////
5
+ //// Role words on an item color its marker; `success`, `warning` and `danger` also change the marker shape, so color is not the only cue.
6
+ ////
7
+ //// With `aria-current="step"` on one item the list becomes a progress rail: earlier items filled, the current one ringed, later ones hollow. Say "completed" in the item text too.
8
+ ////
9
+ //// @since 1.0.0
10
+ //// @needs none
11
+ //// @js none
12
+ //// @example html
13
+ //// <ol class="wu-timeline">
14
+ //// <li class="wu-timeline-item success">
15
+ //// <p class="wu-timeline-title">Order shipped</p>
16
+ //// <time class="wu-timeline-time" datetime="2026-03-02">2 March</time>
17
+ //// <p class="wu-timeline-text">Handed to the carrier.</p>
18
+ //// </li>
19
+ //// <li class="wu-timeline-item danger">
20
+ //// <p class="wu-timeline-title">Payment failed</p>
21
+ //// <time class="wu-timeline-time" datetime="2026-03-01">1 March</time>
22
+ //// </li>
23
+ //// </ol>
24
+ //// @example html
25
+ //// <ol class="wu-timeline">
26
+ //// <li class="wu-timeline-item"><p class="wu-timeline-title">Cart (completed)</p></li>
27
+ //// <li class="wu-timeline-item" aria-current="step"><p class="wu-timeline-title">Payment</p></li>
28
+ //// <li class="wu-timeline-item"><p class="wu-timeline-title">Confirmation</p></li>
29
+ //// </ol>
30
+ //// @modifier brand | info - On an item: marker color @since 1.0.0
31
+ //// @modifier success | warning | danger - On an item: marker color and a check, triangle or octagon shape @since 1.0.0
32
+ //// @modifier animated - Each rail segment draws once as it scrolls into view when the user allows motion; `wu-animated` on an ancestor does the same @since 1.0.0
33
+ //// @part wu-timeline-item - One event: a marker, a rail to the next item and the content @since 1.0.0
34
+ //// @part wu-timeline-title - Event title in medium weight @since 1.0.0
35
+ //// @part wu-timeline-time - Muted time or date line @since 1.0.0
36
+ //// @part wu-timeline-text - Muted description @since 1.0.0
37
+ //// @state aria-current="step" - On an item: the current step, ringed; earlier items fill and later ones turn hollow @since 1.0.0
38
+ ////
39
+
40
+ @use "@wovenui/core/mixins" as woven;
41
+
42
+ $marker: woven.token(space-3);
43
+
44
+ .wu-timeline {
45
+ margin: 0;
46
+ padding: 0;
47
+ color: woven.token(text-regular);
48
+ font-size: woven.token(font-size-sm);
49
+ line-height: woven.token(line-height-normal);
50
+ list-style: none;
51
+ }
52
+
53
+ .wu-timeline-item {
54
+ position: relative;
55
+ padding-block-end: woven.token(space-6);
56
+ padding-inline-start: calc(#{$marker} + #{woven.token(space-3)});
57
+
58
+ // Marker, centered on the first line
59
+ &::before {
60
+ content: "";
61
+ position: absolute;
62
+ inset-block-start: calc((1lh - #{$marker}) / 2);
63
+ inset-inline-start: 0;
64
+ width: $marker;
65
+ height: $marker;
66
+ border-radius: woven.token(radius-full);
67
+ background: woven.token(border-strong);
68
+ }
69
+
70
+ // Rail to the next marker
71
+ &::after {
72
+ content: "";
73
+ position: absolute;
74
+ inset-block: calc((1lh + #{$marker}) / 2 + #{woven.token(space-1)}) calc(#{woven.token(space-1)} - (1lh - #{$marker}) / 2);
75
+ inset-inline-start: calc((#{$marker} - #{woven.token(border-width-2)}) / 2);
76
+ width: woven.token(border-width-2);
77
+ border-radius: woven.token(radius-full);
78
+ background: woven.token(border-regular);
79
+ }
80
+
81
+ &:last-child {
82
+ padding-block-end: 0;
83
+
84
+ &::after {
85
+ content: none;
86
+ }
87
+ }
88
+ }
89
+
90
+ .wu-timeline-title {
91
+ margin: 0;
92
+ font-weight: woven.token(font-weight-medium);
93
+ }
94
+
95
+ .wu-timeline-time {
96
+ display: block;
97
+ color: woven.token(text-muted);
98
+ font-size: woven.token(font-size-xs);
99
+ font-variant-numeric: tabular-nums;
100
+ }
101
+
102
+ .wu-timeline-text {
103
+ margin: 0;
104
+ margin-block-start: woven.token(space-1);
105
+ color: woven.token(text-muted);
106
+ text-wrap: pretty;
107
+ }
108
+
109
+ @each $role in "brand", "success", "danger", "warning", "info" {
110
+ .wu-timeline-item.#{$role}::before {
111
+ background: woven.token($role);
112
+ }
113
+ }
114
+
115
+ // Shape markers from _shared.scss
116
+ .wu-timeline-item:is(.success, .warning, .danger)::before {
117
+ border-radius: 0;
118
+ }
119
+
120
+ .wu-timeline-item.success::before {
121
+ clip-path: var(--wu-kit-marker-check);
122
+ }
123
+
124
+ .wu-timeline-item.warning::before {
125
+ clip-path: var(--wu-kit-marker-triangle);
126
+ }
127
+
128
+ .wu-timeline-item.danger::before {
129
+ clip-path: var(--wu-kit-marker-octagon);
130
+ }
131
+
132
+ // Progress: completed
133
+ .wu-timeline-item:has(~ [aria-current="step"]) {
134
+ &::before,
135
+ &::after {
136
+ background: woven.token(brand);
137
+ }
138
+ }
139
+
140
+ // Progress: current, a ring
141
+ .wu-timeline-item[aria-current="step"] {
142
+ &::before {
143
+ border-radius: woven.token(radius-full);
144
+ clip-path: none;
145
+ background: woven.token(bg-surface);
146
+ box-shadow:
147
+ inset 0 0 0 woven.token(border-width-2) woven.token(brand),
148
+ 0 0 0 woven.token(border-width-4) woven.token(brand-subtle);
149
+ }
150
+
151
+ .wu-timeline-title {
152
+ font-weight: woven.token(font-weight-semibold);
153
+ }
154
+ }
155
+
156
+ // Progress: upcoming, hollow
157
+ .wu-timeline-item[aria-current="step"] ~ .wu-timeline-item {
158
+ color: woven.token(text-muted);
159
+
160
+ &::before {
161
+ border-radius: woven.token(radius-full);
162
+ clip-path: none;
163
+ background: woven.token(bg-surface);
164
+ box-shadow: inset 0 0 0 woven.token(border-width-2) woven.token(border-strong);
165
+ }
166
+ }
167
+
168
+ // Opt-in motion: each rail segment draws once as it scrolls into view
169
+ @media (prefers-reduced-motion: no-preference) {
170
+ .wu-timeline.animated,
171
+ .wu-animated .wu-timeline {
172
+ > .wu-timeline-item::after {
173
+ transform-origin: top;
174
+ animation: wu-timeline-draw woven.token(duration-500) woven.token(ease-out) both;
175
+ }
176
+
177
+ @supports (animation-timeline: view()) {
178
+ > .wu-timeline-item::after {
179
+ animation-duration: auto;
180
+ animation-timing-function: linear;
181
+ animation-timeline: view();
182
+ animation-range: entry 0% cover 40%;
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ @keyframes wu-timeline-draw {
189
+ from {
190
+ scale: 1 0;
191
+ }
192
+
193
+ to {
194
+ scale: 1 1;
195
+ }
196
+ }
@@ -0,0 +1,41 @@
1
+ ////
2
+ //// @component wu-title
3
+ //// Visual heading size on any element, in four steps.
4
+ ////
5
+ //// Pick the heading level for the document outline and the size for the look.
6
+ ////
7
+ //// @since 1.0.0
8
+ //// @needs none
9
+ //// @js none
10
+ //// @example html
11
+ //// <h2 class="wu-title xl">Extra large title</h2>
12
+ //// <h2 class="wu-title">Default title</h2>
13
+ //// @example html
14
+ //// <h3 class="wu-title lg">An h3 with the large size</h3>
15
+ //// @modifier sm - Large size, one step below the default @since 1.0.0
16
+ //// @modifier lg - 2xl size @since 1.0.0
17
+ //// @modifier xl - 3xl size @since 1.0.0
18
+ ////
19
+
20
+ @use "@wovenui/core/mixins" as woven;
21
+
22
+ .wu-title {
23
+ margin: 0;
24
+ color: woven.token(text-regular);
25
+ font-size: woven.token(font-size-xl);
26
+ font-weight: woven.token(font-weight-semibold);
27
+ line-height: woven.token(line-height-tight);
28
+ text-wrap: balance;
29
+ }
30
+
31
+ .wu-title.sm {
32
+ font-size: woven.token(font-size-lg);
33
+ }
34
+
35
+ .wu-title.lg {
36
+ font-size: woven.token(font-size-2xl);
37
+ }
38
+
39
+ .wu-title.xl {
40
+ font-size: woven.token(font-size-3xl);
41
+ }
@@ -0,0 +1,221 @@
1
+ ////
2
+ //// @component wu-toast
3
+ //// Short notification in a corner of the viewport, shown in a polite live region.
4
+ ////
5
+ //// The region sits at the bottom end corner by default and scrolls when the toasts are taller than the viewport. The title text carries the meaning, not the color.
6
+ ////
7
+ //// `toast("Saved")` from the kit script adds a toast, and the region when the page has none. It closes the toast after a delay, pauses on hover and focus, keeps at most three open and skips a repeat of an open message.
8
+ ////
9
+ //// Create the region at page load; a region added together with its first toast may not be announced.
10
+ ////
11
+ //// @since 1.0.0
12
+ //// @needs none
13
+ //// @js required toast, dismiss
14
+ //// @example html
15
+ //// <section class="wu-toast-region animated" aria-live="polite" aria-label="Notifications">
16
+ //// <div class="wu-toast success">
17
+ //// <p class="wu-toast-title">Saved</p>
18
+ //// <p>Your changes are live.</p>
19
+ //// <button class="wu-toast-close" type="button" aria-label="Close"></button>
20
+ //// </div>
21
+ //// </section>
22
+ //// @example html
23
+ //// <section class="wu-toast-region top-end" aria-live="polite" aria-label="Notifications"></section>
24
+ //// @modifier brand | success | danger | warning | info - On a toast: role color on the start edge and the title @since 1.0.0
25
+ //// @modifier top-start | top-end | bottom-start - On the region: moves it to another corner; the default is the bottom end corner @since 1.0.0
26
+ //// @modifier animated - On the region: toasts slide in, fade out and stack behind the newest, fanning out on hover or focus; `wu-animated` on an ancestor does the same @since 1.0.0
27
+ //// @part wu-toast-region - Fixed live region that holds the toasts; hidden when empty @since 1.0.0
28
+ //// @part wu-toast-title - Toast title in semibold weight @since 1.0.0
29
+ //// @part wu-toast-close - Close button with `aria-label`, its cross drawn in CSS @since 1.0.0
30
+ //// @variable --wu-toast--Width [22rem] - Width of the toasts in the region @since 1.0.0
31
+ //// @state hidden - On a toast: removed from layout, after a fade out with `animated` @since 1.0.0
32
+ //// @state :empty - A region with no toasts is hidden @since 1.0.0
33
+ ////
34
+
35
+ @use "@wovenui/core/mixins" as woven;
36
+ @use "../close";
37
+
38
+ $-gap: woven.token(space-2);
39
+ $-edge: woven.token(space-4);
40
+ $-depth: "(sibling-count() - sibling-index())";
41
+
42
+ // The padding keeps the toast shadows inside the scrolling region; the toasts sit $-edge from the viewport edge
43
+ .wu-toast-region {
44
+ position: fixed;
45
+ inset-block-end: 0;
46
+ inset-inline-end: 0;
47
+ z-index: woven.token(z-toast);
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: $-gap;
51
+ inline-size: min(calc(var(--wu-toast--Width, 22rem) + 2 * #{$-edge}), 100%);
52
+ max-block-size: 100dvh;
53
+ padding: $-edge;
54
+ overflow-y: auto;
55
+ overscroll-behavior: contain;
56
+ scrollbar-width: thin;
57
+ pointer-events: none;
58
+
59
+ &:empty {
60
+ display: none;
61
+ }
62
+ }
63
+
64
+ .wu-toast-region.top-start,
65
+ .wu-toast-region.top-end {
66
+ inset-block: 0 auto;
67
+ flex-direction: column-reverse;
68
+ }
69
+
70
+ .wu-toast-region.top-start,
71
+ .wu-toast-region.bottom-start {
72
+ inset-inline: 0 auto;
73
+ }
74
+
75
+ .wu-toast {
76
+ flex: none;
77
+ display: grid;
78
+ grid-template-columns: minmax(0, 1fr) auto;
79
+ column-gap: woven.token(space-2);
80
+ align-items: start;
81
+ padding-block: woven.token(space-3);
82
+ padding-inline: woven.token(space-4) woven.token(space-2);
83
+ border: woven.token(border-width-1) solid woven.token(border-regular);
84
+ border-radius: woven.token(radius-md);
85
+ background: woven.token(bg-raised);
86
+ color: woven.token(text-regular);
87
+ font-size: woven.token(font-size-sm);
88
+ line-height: woven.token(line-height-normal);
89
+ box-shadow: woven.token(shadow-lg);
90
+ pointer-events: auto;
91
+
92
+ &[hidden] {
93
+ display: none;
94
+ }
95
+
96
+ > :not(.wu-toast-close) {
97
+ grid-column: 1;
98
+ margin: 0;
99
+ min-inline-size: 0;
100
+ overflow-wrap: anywhere;
101
+ }
102
+
103
+ > :not(.wu-toast-close) + :not(.wu-toast-close) {
104
+ margin-block-start: woven.token(space-1);
105
+ }
106
+ }
107
+
108
+ .wu-toast-title {
109
+ font-weight: woven.token(font-weight-semibold);
110
+ }
111
+
112
+ @each $role in "brand", "success", "danger", "warning", "info" {
113
+ .wu-toast.#{$role} {
114
+ border-inline-start: woven.token(border-width-4) solid woven.token($role);
115
+
116
+ > .wu-toast-title {
117
+ color: woven.token(text-#{$role});
118
+ }
119
+ }
120
+ }
121
+
122
+ .wu-toast-close {
123
+ @include close.button;
124
+
125
+ grid-area: 1 / 2 / span 9;
126
+ margin-block: calc(-1 * #{woven.token(space-1)});
127
+ }
128
+
129
+ // Stack: older toasts stack behind the newest and fan out while the region
130
+ // is hovered or focused. Layout only; the motion below is separate, so reduced motion keeps the same stack.
131
+ @supports (z-index: sibling-index()) {
132
+ .wu-toast-region.animated,
133
+ .wu-animated .wu-toast-region {
134
+ display: grid;
135
+ max-block-size: none;
136
+ overflow: visible;
137
+ }
138
+
139
+ .wu-toast-region.animated > .wu-toast,
140
+ .wu-animated .wu-toast-region > .wu-toast {
141
+ grid-area: 1 / 1;
142
+ align-self: end;
143
+ transform-origin: 50% 100%;
144
+ opacity: clamp(0, 3 - #{$-depth}, 1);
145
+ translate: 0 calc(#{$-depth} * -1 * #{woven.token(space-3)});
146
+ scale: calc(1 - #{$-depth} * 0.05);
147
+ }
148
+
149
+ // Fully hidden behind the stack: not focusable until the stack fans out
150
+ .wu-toast-region.animated:not(:hover, :focus-within) > .wu-toast:nth-last-child(n + 4),
151
+ .wu-animated .wu-toast-region:not(:hover, :focus-within) > .wu-toast:nth-last-child(n + 4) {
152
+ visibility: hidden;
153
+ }
154
+
155
+ .wu-toast-region.top-start.animated > .wu-toast,
156
+ .wu-toast-region.top-end.animated > .wu-toast,
157
+ .wu-animated .wu-toast-region.top-start > .wu-toast,
158
+ .wu-animated .wu-toast-region.top-end > .wu-toast {
159
+ align-self: start;
160
+ transform-origin: 50% 0;
161
+ translate: 0 calc(#{$-depth} * #{woven.token(space-3)});
162
+ }
163
+
164
+ .wu-toast-region.animated:is(:hover, :focus-within) > .wu-toast,
165
+ .wu-animated .wu-toast-region:is(:hover, :focus-within) > .wu-toast {
166
+ opacity: 1;
167
+ translate: 0 calc(#{$-depth} * (-100% - #{$-gap}));
168
+ scale: 1;
169
+ }
170
+
171
+ .wu-toast-region.top-start.animated:is(:hover, :focus-within) > .wu-toast,
172
+ .wu-toast-region.top-end.animated:is(:hover, :focus-within) > .wu-toast,
173
+ .wu-animated .wu-toast-region.top-start:is(:hover, :focus-within) > .wu-toast,
174
+ .wu-animated .wu-toast-region.top-end:is(:hover, :focus-within) > .wu-toast {
175
+ translate: 0 calc(#{$-depth} * (100% + #{$-gap}));
176
+ }
177
+ }
178
+
179
+ // Opt-in motion: toasts slide in, fade out, and glide between stack positions
180
+ @media (prefers-reduced-motion: no-preference) {
181
+ .wu-toast-region.animated > .wu-toast,
182
+ .wu-animated .wu-toast-region > .wu-toast {
183
+ transition:
184
+ opacity woven.token(duration-300) woven.token(ease-out),
185
+ translate woven.token(duration-500) var(--wu-kit-ease-spring),
186
+ scale woven.token(duration-500) var(--wu-kit-ease-spring),
187
+ visibility woven.token(duration-300) allow-discrete,
188
+ display woven.token(duration-300) allow-discrete;
189
+ }
190
+
191
+ // Entry: after the stack rules, so the slide wins on the first frame
192
+ .wu-toast-region.animated > .wu-toast,
193
+ .wu-animated .wu-toast-region > .wu-toast {
194
+ @starting-style {
195
+ opacity: 0;
196
+ translate: 0 100%;
197
+ }
198
+ }
199
+
200
+ .wu-toast-region.top-start.animated > .wu-toast,
201
+ .wu-toast-region.top-end.animated > .wu-toast,
202
+ .wu-animated .wu-toast-region.top-start > .wu-toast,
203
+ .wu-animated .wu-toast-region.top-end > .wu-toast {
204
+ @starting-style {
205
+ opacity: 0;
206
+ translate: 0 -100%;
207
+ }
208
+ }
209
+
210
+ // Closing: fade out before removal
211
+ .wu-toast-region.animated > .wu-toast[hidden],
212
+ .wu-animated .wu-toast-region > .wu-toast[hidden] {
213
+ opacity: 0;
214
+ }
215
+ }
216
+
217
+ @media (forced-colors: active) {
218
+ .wu-toast {
219
+ border-inline-start-width: woven.token(border-width-4);
220
+ }
221
+ }
@@ -0,0 +1,171 @@
1
+ ////
2
+ //// @component wu-toggle
3
+ //// On and off switch built on a native checkbox with `role="switch"`.
4
+ ////
5
+ //// The track is 2.25em by 1.25em with a 1em thumb, so it follows the root type size and the `sm` and `lg` steps.
6
+ ////
7
+ //// @since 1.0.0
8
+ //// @needs none
9
+ //// @js none
10
+ //// @example html
11
+ //// <label class="wu-toggle">
12
+ //// <input class="wu-toggle-input" type="checkbox" role="switch" checked>
13
+ //// <span class="wu-toggle-label">Email notifications</span>
14
+ //// </label>
15
+ //// @example html
16
+ //// <label class="wu-toggle lg animated">
17
+ //// <input class="wu-toggle-input" type="checkbox" role="switch" aria-invalid="true">
18
+ //// <span class="wu-toggle-label">Accept the terms</span>
19
+ //// </label>
20
+ //// @modifier sm | lg - Smaller or larger switch and label @since 1.0.0
21
+ //// @modifier animated - Spring thumb when the user allows motion; `wu-animated` on an ancestor does the same @since 1.0.0
22
+ //// @part wu-toggle-input - The checkbox: track and thumb @since 1.0.0
23
+ //// @part wu-toggle-label - Label text beside the switch @since 1.0.0
24
+ //// @state :checked - Brand track and the thumb moved to the end @since 1.0.0
25
+ //// @state disabled - Half opacity and a not-allowed cursor @since 1.0.0
26
+ //// @state aria-invalid="true" | :user-invalid - Danger outline @since 1.0.0
27
+ ////
28
+
29
+ @use "@wovenui/core/mixins" as woven;
30
+
31
+ .wu-toggle {
32
+ display: inline-flex;
33
+ align-items: flex-start;
34
+ gap: woven.token(space-2);
35
+ color: woven.token(text-regular);
36
+ font-size: woven.token(font-size-md);
37
+ line-height: woven.token(line-height-normal);
38
+ cursor: pointer;
39
+
40
+ &:has(.wu-toggle-input:disabled) {
41
+ opacity: woven.token(opacity-50);
42
+ cursor: not-allowed;
43
+ }
44
+ }
45
+
46
+ .wu-toggle-input {
47
+ display: inline-grid;
48
+ flex: none;
49
+ place-items: center start;
50
+ width: 2.25em;
51
+ height: 1.25em;
52
+ margin: 0;
53
+ margin-block-start: calc((1lh - 1.25em) / 2);
54
+ padding-inline: 0.125em;
55
+ border: 0;
56
+ border-radius: woven.token(radius-full);
57
+ background: woven.token(border-control);
58
+ font-size: inherit;
59
+ line-height: inherit;
60
+ vertical-align: middle;
61
+ appearance: none;
62
+ cursor: pointer;
63
+ transition-property: background-color;
64
+ transition-duration: woven.token(duration-150);
65
+ transition-timing-function: woven.token(ease-out);
66
+
67
+ // Thumb
68
+ &::before {
69
+ content: "";
70
+ width: 1em;
71
+ height: 1em;
72
+ border-radius: woven.token(radius-full);
73
+ background: woven.token(bg-surface);
74
+ box-shadow: woven.token(shadow-sm);
75
+ }
76
+
77
+ &:hover:not(:disabled, :checked) {
78
+ background: woven.token(border-control-hover);
79
+ }
80
+
81
+ &:is([aria-invalid="true"], :user-invalid) {
82
+ outline: woven.token(border-width-1) solid woven.token(danger);
83
+ outline-offset: woven.token(border-width-1);
84
+ }
85
+
86
+ &:focus-visible {
87
+ outline: woven.token(border-focus-width) solid woven.token(border-focus);
88
+ outline-offset: woven.token(border-focus-offset);
89
+ }
90
+
91
+ &:checked {
92
+ background: woven.token(brand);
93
+
94
+ &::before {
95
+ background: woven.token(text-on-brand);
96
+ translate: 1em 0;
97
+ }
98
+
99
+ &:hover:not(:disabled) {
100
+ background: woven.token(brand-hover);
101
+ }
102
+ }
103
+
104
+ &:checked:dir(rtl)::before {
105
+ translate: -1em 0;
106
+ }
107
+
108
+ &:disabled {
109
+ opacity: woven.token(opacity-50);
110
+ cursor: not-allowed;
111
+ }
112
+
113
+ .wu-toggle > &:disabled {
114
+ opacity: 1;
115
+ }
116
+ }
117
+
118
+ .wu-toggle-label {
119
+ min-width: 0;
120
+ }
121
+
122
+ .wu-toggle.sm {
123
+ font-size: woven.token(font-size-sm);
124
+ }
125
+
126
+ .wu-toggle.lg {
127
+ font-size: woven.token(font-size-lg);
128
+ }
129
+
130
+ // Calm default: the thumb slides without overshoot
131
+ @media (prefers-reduced-motion: no-preference) {
132
+ .wu-toggle-input::before {
133
+ transition: translate woven.token(duration-150) woven.token(ease-out);
134
+ }
135
+
136
+ // Opt-in motion: spring thumb
137
+ .wu-toggle.animated .wu-toggle-input::before,
138
+ .wu-animated .wu-toggle-input::before {
139
+ transition: translate woven.token(duration-500) var(--wu-kit-ease-spring);
140
+ }
141
+ }
142
+
143
+ @media (forced-colors: active) {
144
+ .wu-toggle-input {
145
+ padding-inline: calc(0.125em - #{woven.token(border-width-1)});
146
+ border: woven.token(border-width-1) solid CanvasText;
147
+ background: Canvas;
148
+ forced-color-adjust: none;
149
+
150
+ &::before {
151
+ background: CanvasText;
152
+ }
153
+
154
+ &:checked {
155
+ border-color: Highlight;
156
+ background: Highlight;
157
+
158
+ &::before {
159
+ background: HighlightText;
160
+ }
161
+ }
162
+
163
+ &:disabled {
164
+ border-color: GrayText;
165
+
166
+ &::before {
167
+ background: GrayText;
168
+ }
169
+ }
170
+ }
171
+ }