@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,115 @@
1
+ ////
2
+ //// @component wu-breadcrumb
3
+ //// Trail of links that shows where the page sits in the site.
4
+ ////
5
+ //// Wrap an ordered list in a `nav` with an `aria-label`, and mark the last item with `aria-current="page"`.
6
+ ////
7
+ //// @since 1.0.0
8
+ //// @needs none
9
+ //// @js none
10
+ //// @example html
11
+ //// <nav class="wu-breadcrumb" aria-label="Breadcrumb">
12
+ //// <ol class="wu-breadcrumb-list">
13
+ //// <li class="wu-breadcrumb-item"><a class="wu-breadcrumb-link" href="/">Home</a></li>
14
+ //// <li class="wu-breadcrumb-item"><a class="wu-breadcrumb-link" href="/projects/">Projects</a></li>
15
+ //// <li class="wu-breadcrumb-item"><span aria-current="page">Website</span></li>
16
+ //// </ol>
17
+ //// </nav>
18
+ //// @example html
19
+ //// <nav class="wu-breadcrumb slash sm" aria-label="Breadcrumb">
20
+ //// <ol class="wu-breadcrumb-list">
21
+ //// <li class="wu-breadcrumb-item"><a class="wu-breadcrumb-link" href="/">Home</a></li>
22
+ //// <li class="wu-breadcrumb-item"><span aria-current="page">Docs</span></li>
23
+ //// </ol>
24
+ //// </nav>
25
+ //// @modifier slash - A slash separator instead of the chevron @since 1.0.0
26
+ //// @modifier sm - Smaller font size @since 1.0.0
27
+ //// @part wu-breadcrumb-list - The `ol` that lays the items out and wraps them @since 1.0.0
28
+ //// @part wu-breadcrumb-item - One `li` in the trail, with a separator before it @since 1.0.0
29
+ //// @part wu-breadcrumb-link - Underlined link to a parent page @since 1.0.0
30
+ //// @state aria-current="page" - The current page: regular text weight and color, no underline @since 1.0.0
31
+ ////
32
+
33
+ @use "@wovenui/core/mixins" as woven;
34
+
35
+ .wu-breadcrumb {
36
+ color: woven.token(text-muted);
37
+ font-size: woven.token(font-size-sm);
38
+ line-height: woven.token(line-height-normal);
39
+ }
40
+
41
+ .wu-breadcrumb-list {
42
+ display: flex;
43
+ flex-wrap: wrap;
44
+ align-items: center;
45
+ gap: woven.token(space-1) woven.token(space-2);
46
+ margin: 0;
47
+ padding: 0;
48
+ list-style: none;
49
+ }
50
+
51
+ .wu-breadcrumb-item {
52
+ display: inline-flex;
53
+ align-items: center;
54
+ gap: woven.token(space-2);
55
+ min-width: 0;
56
+ }
57
+
58
+ // Separator: a chevron drawn with two borders, mirrored in RTL
59
+ .wu-breadcrumb-item + .wu-breadcrumb-item::before {
60
+ content: "";
61
+ flex: none;
62
+ width: 0.4em;
63
+ height: 0.4em;
64
+ border-block-start: woven.token(border-width-1) solid currentcolor;
65
+ border-inline-end: woven.token(border-width-1) solid currentcolor;
66
+ color: woven.token(text-subtle);
67
+ rotate: 45deg;
68
+ }
69
+
70
+ .wu-breadcrumb-item + .wu-breadcrumb-item:dir(rtl)::before {
71
+ rotate: -45deg;
72
+ }
73
+
74
+ .wu-breadcrumb.slash .wu-breadcrumb-item + .wu-breadcrumb-item::before {
75
+ content: "/";
76
+ content: "/" / "";
77
+ width: auto;
78
+ height: auto;
79
+ border: 0;
80
+ rotate: none;
81
+ }
82
+
83
+ .wu-breadcrumb.slash .wu-breadcrumb-item + .wu-breadcrumb-item:dir(rtl)::before {
84
+ rotate: none;
85
+ }
86
+
87
+ // Underlined at rest so links read as links; the current page is plain text
88
+ .wu-breadcrumb-link {
89
+ border-radius: woven.token(radius-xs);
90
+ color: inherit;
91
+ text-decoration: underline;
92
+ text-decoration-color: woven.token(border-strong);
93
+ text-decoration-thickness: woven.token(border-width-1);
94
+ text-underline-offset: 0.2em;
95
+
96
+ &:hover {
97
+ color: woven.token(text-regular);
98
+ text-decoration-color: currentcolor;
99
+ }
100
+
101
+ &:focus-visible {
102
+ outline: woven.token(border-focus-width) solid woven.token(border-focus);
103
+ outline-offset: woven.token(border-focus-offset);
104
+ }
105
+ }
106
+
107
+ .wu-breadcrumb-item [aria-current="page"] {
108
+ color: woven.token(text-regular);
109
+ font-weight: woven.token(font-weight-medium);
110
+ text-decoration: none;
111
+ }
112
+
113
+ .wu-breadcrumb.sm {
114
+ font-size: woven.token(font-size-xs);
115
+ }
@@ -0,0 +1,113 @@
1
+ ////
2
+ //// @component wu-btn-group
3
+ //// Joins buttons into one segmented control, as a split button, a toggle set or a radio set.
4
+ ////
5
+ //// Split divider: use `brand`, `danger` or `quiet` on both buttons of a pair. Checkable: use `role="group"` with checkboxes or `role="radiogroup"` with radios.
6
+ ////
7
+ //// The checked label takes the `aria-pressed` look; arrow keys are native for radios. `animated` on a button gives it the press from `wu-btn`.
8
+ ////
9
+ //// @since 1.0.0
10
+ //// @needs button
11
+ //// @js none
12
+ //// @example html
13
+ //// <div class="wu-btn-group">
14
+ //// <button class="wu-btn brand" type="button">Save</button>
15
+ //// <button class="wu-btn brand caret" type="button" popovertarget="more-save" aria-label="More save options"></button>
16
+ //// </div>
17
+ //// <div id="more-save" popover>Save as draft</div>
18
+ //// @example html
19
+ //// <div class="wu-btn-group" role="radiogroup" aria-label="Align">
20
+ //// <label class="wu-btn"><input type="radio" name="align" class="sr-only" checked> Left</label>
21
+ //// <label class="wu-btn"><input type="radio" name="align" class="sr-only"> Right</label>
22
+ //// </div>
23
+ //// @example html
24
+ //// <div class="wu-btn-group vertical" role="group" aria-label="View">
25
+ //// <button class="wu-btn" type="button" aria-pressed="true">List</button>
26
+ //// <button class="wu-btn" type="button" aria-pressed="false">Grid</button>
27
+ //// </div>
28
+ //// @modifier vertical - Stacks the buttons in a column @since 1.0.0
29
+ //// @modifier brand | danger | quiet - On two neighbor buttons: a divider line between them, for split buttons @since 1.0.0
30
+ //// @state aria-pressed="true" - Toggle on: selected tint and an inner ring; a checked input inside the label does the same @since 1.0.0
31
+ ////
32
+
33
+ @use "@wovenui/core/mixins" as woven;
34
+
35
+ .wu-btn-group {
36
+ display: inline-flex;
37
+ vertical-align: middle;
38
+
39
+ > .wu-btn {
40
+ position: relative;
41
+ border-radius: 0;
42
+ }
43
+
44
+ > .wu-btn:is(:hover, :focus-visible, [aria-pressed="true"], :has(> input:checked, > input:focus-visible)) {
45
+ z-index: woven.token(z-hover);
46
+ }
47
+
48
+ // Toggle state: tint plus an inner ring, so color is not the only signal
49
+ > .wu-btn:is([aria-pressed="true"], :has(> input:checked)) {
50
+ background: woven.token(bg-selected);
51
+ box-shadow: inset 0 0 0 woven.token(border-width-1) woven.token(brand);
52
+ color: woven.token(text-brand);
53
+ }
54
+
55
+ > .wu-btn:has(> input:focus-visible) {
56
+ outline: woven.token(border-focus-width) solid woven.token(border-focus);
57
+ outline-offset: woven.token(border-focus-offset);
58
+ }
59
+
60
+ > .wu-btn:has(> input:disabled) {
61
+ opacity: woven.token(opacity-50);
62
+ pointer-events: none;
63
+ }
64
+
65
+ // Split button divider
66
+ > .wu-btn.brand + .wu-btn.brand {
67
+ border-inline-start-color: color-mix(in srgb, woven.token(text-on-brand) 40%, transparent);
68
+ }
69
+
70
+ > .wu-btn.danger + .wu-btn.danger {
71
+ border-inline-start-color: color-mix(in srgb, woven.token(text-on-danger) 40%, transparent);
72
+ }
73
+
74
+ > .wu-btn.quiet + .wu-btn.quiet {
75
+ border-inline-start-color: woven.token(border-regular);
76
+ }
77
+ }
78
+
79
+ .wu-btn-group:not(.vertical) > .wu-btn {
80
+ &:first-child {
81
+ border-start-start-radius: woven.token(radius-md);
82
+ border-end-start-radius: woven.token(radius-md);
83
+ }
84
+
85
+ &:nth-last-child(1 of .wu-btn) {
86
+ border-start-end-radius: woven.token(radius-md);
87
+ border-end-end-radius: woven.token(radius-md);
88
+ }
89
+
90
+ & + .wu-btn {
91
+ margin-inline-start: calc(-1 * #{woven.token(border-width-1)});
92
+ }
93
+ }
94
+
95
+ .wu-btn-group.vertical {
96
+ flex-direction: column;
97
+
98
+ > .wu-btn:first-child {
99
+ border-start-start-radius: woven.token(radius-md);
100
+ border-start-end-radius: woven.token(radius-md);
101
+ }
102
+
103
+ > .wu-btn:nth-last-child(1 of .wu-btn) {
104
+ border-end-start-radius: woven.token(radius-md);
105
+ border-end-end-radius: woven.token(radius-md);
106
+ }
107
+
108
+ > .wu-btn + .wu-btn {
109
+ margin-block-start: calc(-1 * #{woven.token(border-width-1)});
110
+ }
111
+ }
112
+
113
+ // Opt-in motion: animated gives each button the .wu-btn.animated press, set in _button.scss
@@ -0,0 +1,301 @@
1
+ ////
2
+ //// @component wu-btn
3
+ //// Buttons for actions, in roles and sizes, with a caret for menus and split buttons.
4
+ ////
5
+ //// A `button` element is the default; an `a` element with `wu-btn` looks the same for navigation.
6
+ ////
7
+ //// Inside a `wu-field-group sm` or `lg`, a button without its own size takes the group size.
8
+ ////
9
+ //// @since 1.0.0
10
+ //// @needs none
11
+ //// @js none
12
+ //// @example html
13
+ //// <button class="wu-btn" type="button">Cancel</button>
14
+ //// <button class="wu-btn brand" type="button">Save</button>
15
+ //// <button class="wu-btn danger subtle sm" type="button">Delete</button>
16
+ //// @example html
17
+ //// <button class="wu-btn icon round" type="button" aria-label="Settings">
18
+ //// <svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4" /></svg>
19
+ //// </button>
20
+ //// <button class="wu-btn brand" type="button" aria-busy="true">Saving</button>
21
+ //// @example html
22
+ //// <button class="wu-btn caret" type="button" popovertarget="actions">Actions</button>
23
+ //// <div id="actions" popover>Menu content</div>
24
+ //// @modifier brand - Solid brand fill for the main action @since 1.0.0
25
+ //// @modifier danger - Solid danger fill for destructive actions @since 1.0.0
26
+ //// @modifier subtle - With `brand` or `danger`: tinted fill and border, solid on hover @since 1.0.0
27
+ //// @modifier quiet - No border or fill until hover @since 1.0.0
28
+ //// @modifier link - Looks like a link, no padding, keeps the control height @since 1.0.0
29
+ //// @modifier round - Pill shape; a circle when the button is empty or holds only an svg @since 1.0.0
30
+ //// @modifier icon - Square button for one svg and no text; give it an `aria-label` @since 1.0.0
31
+ //// @modifier caret - Adds a caret after the label; an empty caret button with an `aria-label` is a narrow caret-only button for split buttons @since 1.0.0
32
+ //// @modifier sm | lg - Small or large control height and font size @since 1.0.0
33
+ //// @modifier animated - Spring press scale when the user allows motion; `wu-animated` on an ancestor or `wu-btn-group animated` does the same @since 1.0.0
34
+ //// @variable --wu-btn--CaretSize [0.4em] - Size of the caret @since 1.0.0
35
+ //// @state disabled | aria-disabled="true" - Half opacity, no pointer events @since 1.0.0
36
+ //// @state aria-busy="true" - Loading: blocks pointer clicks and keeps full opacity @since 1.0.0
37
+ //// @state :popover-open - The caret turns while the popover right after the button is open @since 1.0.0
38
+ ////
39
+
40
+ @use "@wovenui/core/mixins" as woven;
41
+
42
+ .wu-btn {
43
+ display: inline-flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ gap: woven.token(space-2);
47
+ min-height: woven.token(control-height-md);
48
+ padding-inline: woven.token(control-padding-inline-md);
49
+ border: woven.token(border-width-1) solid woven.token(border-regular);
50
+ border-radius: woven.token(radius-md);
51
+ background: woven.token(bg-surface);
52
+ color: woven.token(text-regular);
53
+ font: inherit;
54
+ font-size: woven.token(font-size-sm);
55
+ font-weight: woven.token(font-weight-medium);
56
+ line-height: 1;
57
+ text-decoration: none;
58
+ white-space: nowrap;
59
+ cursor: pointer;
60
+ transition-property: background-color, border-color, color;
61
+ transition-duration: woven.token(duration-150);
62
+ transition-timing-function: woven.token(ease-out);
63
+
64
+ &:hover {
65
+ border-color: woven.token(border-strong);
66
+ background: woven.token(bg-hover);
67
+ }
68
+
69
+ &:focus-visible {
70
+ outline: woven.token(border-focus-width) solid woven.token(border-focus);
71
+ outline-offset: woven.token(border-focus-offset);
72
+ }
73
+
74
+ &:disabled,
75
+ &[aria-disabled="true"] {
76
+ opacity: woven.token(opacity-50);
77
+ pointer-events: none;
78
+ }
79
+
80
+ &[aria-busy="true"] {
81
+ opacity: 1;
82
+ pointer-events: none;
83
+ }
84
+ }
85
+
86
+ .wu-btn.brand {
87
+ border-color: woven.token(brand);
88
+ background: woven.token(brand);
89
+ color: woven.token(text-on-brand);
90
+
91
+ &:hover {
92
+ border-color: woven.token(brand-hover);
93
+ background: woven.token(brand-hover);
94
+ }
95
+
96
+ &:active {
97
+ border-color: woven.token(brand-active);
98
+ background: woven.token(brand-active);
99
+ }
100
+ }
101
+
102
+ .wu-btn.danger {
103
+ border-color: woven.token(danger);
104
+ background: woven.token(danger);
105
+ color: woven.token(text-on-danger);
106
+
107
+ &:hover {
108
+ border-color: woven.token(danger-hover);
109
+ background: woven.token(danger-hover);
110
+ }
111
+
112
+ &:active {
113
+ border-color: woven.token(danger-active);
114
+ background: woven.token(danger-active);
115
+ }
116
+ }
117
+
118
+ // Subtle: tinted role fill and border, solid on hover
119
+ .wu-btn.brand.subtle {
120
+ border-color: woven.token(brand-border);
121
+ background: woven.token(brand-subtle);
122
+ color: woven.token(text-brand);
123
+
124
+ &:hover {
125
+ border-color: woven.token(brand-hover);
126
+ background: woven.token(brand-hover);
127
+ color: woven.token(text-on-brand);
128
+ }
129
+
130
+ &:active {
131
+ border-color: woven.token(brand-active);
132
+ background: woven.token(brand-active);
133
+ color: woven.token(text-on-brand);
134
+ }
135
+ }
136
+
137
+ .wu-btn.danger.subtle {
138
+ border-color: woven.token(danger-border);
139
+ background: woven.token(danger-subtle);
140
+ color: woven.token(text-danger);
141
+
142
+ &:hover {
143
+ border-color: woven.token(danger-hover);
144
+ background: woven.token(danger-hover);
145
+ color: woven.token(text-on-danger);
146
+ }
147
+
148
+ &:active {
149
+ border-color: woven.token(danger-active);
150
+ background: woven.token(danger-active);
151
+ color: woven.token(text-on-danger);
152
+ }
153
+ }
154
+
155
+ .wu-btn > svg {
156
+ flex-shrink: 0;
157
+ width: 1em;
158
+ height: 1em;
159
+ }
160
+
161
+ .wu-btn.quiet {
162
+ border-color: transparent;
163
+ background: transparent;
164
+
165
+ &:hover {
166
+ border-color: transparent;
167
+ background: woven.token(bg-hover);
168
+ }
169
+ }
170
+
171
+ .wu-btn.sm {
172
+ min-height: woven.token(control-height-sm);
173
+ padding-inline: woven.token(control-padding-inline-sm);
174
+ font-size: woven.token(font-size-xs);
175
+ }
176
+
177
+ .wu-btn.lg {
178
+ min-height: woven.token(control-height-lg);
179
+ padding-inline: woven.token(control-padding-inline-lg);
180
+ font-size: woven.token(font-size-md);
181
+ }
182
+
183
+ .wu-btn.caret::after {
184
+ content: "";
185
+ inline-size: var(--wu-btn--CaretSize, 0.4em);
186
+ block-size: var(--wu-btn--CaretSize, 0.4em);
187
+ margin-block-start: -0.2em;
188
+ border-bottom: 0.125em solid currentcolor;
189
+ border-right: 0.125em solid currentcolor;
190
+ rotate: 45deg;
191
+ }
192
+
193
+ // Link: text only, no padding; keeps the control height so it lines up in a toolbar row
194
+ .wu-btn.link {
195
+ padding-inline: 0;
196
+ border-color: transparent;
197
+ background: transparent;
198
+ color: woven.token(text-link);
199
+
200
+ &:hover {
201
+ border-color: transparent;
202
+ background: transparent;
203
+ color: woven.token(text-link-hover);
204
+ text-decoration: underline;
205
+ text-underline-offset: 0.2em;
206
+ }
207
+ }
208
+
209
+ // Icon: square box for one svg and no text; round makes it a circle
210
+ .wu-btn.icon {
211
+ min-inline-size: woven.token(control-height-md);
212
+ padding: 0;
213
+
214
+ > svg {
215
+ width: 1.25em;
216
+ height: 1.25em;
217
+ }
218
+ }
219
+
220
+ .wu-btn.icon.sm {
221
+ min-inline-size: woven.token(control-height-sm);
222
+ }
223
+
224
+ .wu-btn.icon.lg {
225
+ min-inline-size: woven.token(control-height-lg);
226
+ }
227
+
228
+ // Round: pill; a circle when empty or holding only an svg
229
+ .wu-btn.round {
230
+ border-radius: woven.token(radius-full);
231
+ }
232
+
233
+ .wu-btn.round:empty,
234
+ .wu-btn.round:has(> svg:only-child) {
235
+ min-inline-size: woven.token(control-height-md);
236
+ padding-inline: 0;
237
+ }
238
+
239
+ .wu-btn.round.sm:empty,
240
+ .wu-btn.round.sm:has(> svg:only-child) {
241
+ min-inline-size: woven.token(control-height-sm);
242
+ }
243
+
244
+ .wu-btn.round.lg:empty,
245
+ .wu-btn.round.lg:has(> svg:only-child) {
246
+ min-inline-size: woven.token(control-height-lg);
247
+ }
248
+
249
+ // Caret-only: square, matched to the control height of each size
250
+ .wu-btn.caret:empty {
251
+ min-inline-size: woven.token(control-height-md);
252
+ padding-inline: 0;
253
+ }
254
+
255
+ .wu-btn.caret.sm:empty {
256
+ min-inline-size: woven.token(control-height-sm);
257
+ }
258
+
259
+ .wu-btn.caret.lg:empty {
260
+ min-inline-size: woven.token(control-height-lg);
261
+ }
262
+
263
+ .wu-btn.caret:has(+ [popover]:popover-open)::after {
264
+ margin-block-start: 0.2em;
265
+ rotate: 225deg;
266
+ }
267
+
268
+ // Size from a wu-field-group sm or lg; a button with its own sm or lg keeps it
269
+ @each $size, $font in ("sm": "font-size-xs", "lg": "font-size-md") {
270
+ $h: woven.token(control-height-#{$size});
271
+
272
+ :where(.wu-field-group.#{$size}) {
273
+ .wu-btn:not(.sm, .lg) {
274
+ min-height: $h;
275
+ font-size: woven.token(#{$font});
276
+ }
277
+
278
+ .wu-btn:not(.sm, .lg, .icon, .link, .caret:empty, .round:empty, .round:has(> svg:only-child)) {
279
+ padding-inline: woven.token(control-padding-inline-#{$size});
280
+ }
281
+
282
+ .wu-btn:not(.sm, .lg):is(.icon, .caret:empty, .round:empty, .round:has(> svg:only-child)) {
283
+ min-inline-size: $h;
284
+ }
285
+ }
286
+ }
287
+
288
+ // Opt-in motion: spring press scale, also for every button of an animated group
289
+ @media (prefers-reduced-motion: no-preference) {
290
+ .wu-btn.animated,
291
+ .wu-btn-group.animated > .wu-btn,
292
+ .wu-animated .wu-btn {
293
+ transition-property: background-color, border-color, color, scale;
294
+ transition-duration: woven.token(duration-150), woven.token(duration-150), woven.token(duration-150), woven.token(duration-500);
295
+ transition-timing-function: woven.token(ease-out), woven.token(ease-out), woven.token(ease-out), var(--wu-kit-ease-spring);
296
+
297
+ &:active {
298
+ scale: 0.95;
299
+ }
300
+ }
301
+ }
@@ -0,0 +1,120 @@
1
+ ////
2
+ //// @component wu-card
3
+ //// Bordered surface that groups a header, a body and a footer.
4
+ ////
5
+ //// Put one real link, usually in the title, on `wu-card-link` to make the whole card clickable. Other controls stay clickable above it.
6
+ ////
7
+ //// @since 1.0.0
8
+ //// @needs none
9
+ //// @js none
10
+ //// @example html
11
+ //// <div class="wu-card">
12
+ //// <div class="wu-card-header"><h3 class="wu-card-title">Plan</h3></div>
13
+ //// <div class="wu-card-body">Everything you need to start.</div>
14
+ //// <div class="wu-card-footer"><button class="wu-btn brand" type="button">Choose</button></div>
15
+ //// </div>
16
+ //// @example html
17
+ //// <div class="wu-card">
18
+ //// <div class="wu-card-body">
19
+ //// <h3 class="wu-card-title"><a class="wu-card-link" href="/docs/">Read the docs</a></h3>
20
+ //// <p>Guides for every component.</p>
21
+ //// </div>
22
+ //// </div>
23
+ //// @part wu-card-header - Top row for a title and actions, with a bottom border @since 1.0.0
24
+ //// @part wu-card-title - Heading inside the header or body @since 1.0.0
25
+ //// @part wu-card-body - Main content; it grows so footers line up across a row of cards @since 1.0.0
26
+ //// @part wu-card-footer - Bottom row for actions, aligned to the end, with a top border @since 1.0.0
27
+ //// @part wu-card-link - Link stretched over the whole card @since 1.0.0
28
+ //// @variable --wu-card--Padding [var(--wu-space-5)] - Padding of the header, body and footer @since 1.0.0
29
+ ////
30
+
31
+ @use "@wovenui/core/mixins" as woven;
32
+
33
+ .wu-card {
34
+ display: flex;
35
+ flex-direction: column;
36
+ position: relative;
37
+ border: woven.token(border-width-1) solid woven.token(border-regular);
38
+ border-radius: woven.token(radius-lg);
39
+ background: woven.token(bg-surface);
40
+ color: woven.token(text-regular);
41
+ box-shadow: woven.token(shadow-sm);
42
+ }
43
+
44
+ .wu-card-header,
45
+ .wu-card-body,
46
+ .wu-card-footer {
47
+ padding: var(--wu-card--Padding, #{woven.token(space-5)});
48
+ }
49
+
50
+ .wu-card-header {
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: space-between;
54
+ gap: woven.token(space-3);
55
+ border-block-end: woven.token(border-width-1) solid woven.token(border-regular);
56
+ }
57
+
58
+ .wu-card-title {
59
+ margin: 0;
60
+ font-size: woven.token(font-size-md);
61
+ font-weight: woven.token(font-weight-semibold);
62
+ line-height: woven.token(line-height-tight);
63
+ }
64
+
65
+ // Grows so footers line up across a row of cards
66
+ .wu-card-body {
67
+ flex-grow: 1;
68
+ }
69
+
70
+ .wu-card-footer {
71
+ display: flex;
72
+ flex-wrap: wrap;
73
+ align-items: center;
74
+ justify-content: flex-end;
75
+ gap: woven.token(space-2);
76
+ border-block-start: woven.token(border-width-1) solid woven.token(border-regular);
77
+ }
78
+
79
+ // Whole-card link: one real link (usually in the title) stretched over the card
80
+ .wu-card-link {
81
+ color: inherit;
82
+ text-decoration: none;
83
+
84
+ &::after {
85
+ content: "";
86
+ position: absolute;
87
+ inset: 0;
88
+ border-radius: inherit;
89
+ }
90
+
91
+ &:focus-visible {
92
+ outline: none;
93
+ }
94
+
95
+ &:focus-visible::after {
96
+ outline: woven.token(border-focus-width) solid woven.token(border-focus);
97
+ outline-offset: woven.token(border-focus-offset);
98
+ }
99
+ }
100
+
101
+ .wu-card:has(.wu-card-link) {
102
+ transition-property: border-color, box-shadow;
103
+ transition-duration: woven.token(duration-150);
104
+ transition-timing-function: woven.token(ease-out);
105
+
106
+ // Other controls stay clickable above the stretched link
107
+ :is(a, button, input, select, textarea, summary, label):not(.wu-card-link) {
108
+ position: relative;
109
+ z-index: woven.token(z-hover);
110
+ }
111
+ }
112
+
113
+ .wu-card:has(.wu-card-link:hover) {
114
+ border-color: woven.token(border-strong);
115
+ box-shadow: woven.token(shadow-md);
116
+
117
+ .wu-card-link {
118
+ text-decoration: underline;
119
+ }
120
+ }