@greese/ui 1.1.0 → 1.2.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 (194) hide show
  1. package/README.md +19 -14
  2. package/dist/_maps.scss +1 -1
  3. package/dist/_tokens-base.scss +30 -6
  4. package/dist/_tokens.scss +31 -2
  5. package/dist/_utilities-table.scss +1615 -0
  6. package/dist/clipboard.d.ts +18 -0
  7. package/dist/clipboard.js +62 -0
  8. package/dist/css/Button.css +2 -2
  9. package/dist/css/Carousel.css +145 -47
  10. package/dist/css/Checkbox.css +2 -2
  11. package/dist/css/Chip.css +38 -8
  12. package/dist/css/ChoiceGroup.css +42 -1
  13. package/dist/css/Collapsible.css +1 -0
  14. package/dist/css/ColorPicker.css +84 -0
  15. package/dist/css/Combobox.css +2 -2
  16. package/dist/css/Dialog.css +24 -22
  17. package/dist/css/EditableField.css +219 -0
  18. package/dist/css/Input.css +11 -2
  19. package/dist/css/Menu.css +9 -0
  20. package/dist/css/NumberField.css +53 -8
  21. package/dist/css/NumberStepper.css +9 -0
  22. package/dist/css/OtpField.css +1 -1
  23. package/dist/css/Radio.css +2 -2
  24. package/dist/css/ScrollCarousel.css +225 -0
  25. package/dist/css/SegmentedControl.css +2 -2
  26. package/dist/css/Select.css +3 -2
  27. package/dist/css/Slider.css +45 -5
  28. package/dist/css/Switch.css +6 -6
  29. package/dist/css/Text.css +14 -14
  30. package/dist/css/Textarea.css +1 -1
  31. package/dist/css/core.css +36 -6
  32. package/dist/css/resets.css +162 -0
  33. package/dist/css.d.ts +2 -0
  34. package/dist/css.js +2 -0
  35. package/dist/greese.css +978 -138
  36. package/dist/index.d.ts +2 -1
  37. package/dist/index.js +2 -1
  38. package/dist/scss/components/_Accordion.scss +2 -0
  39. package/dist/scss/components/_ActionSelect.scss +2 -0
  40. package/dist/scss/components/_Alert.scss +2 -0
  41. package/dist/scss/components/_AppBar.scss +2 -0
  42. package/dist/scss/components/_AspectRatio.scss +2 -0
  43. package/dist/scss/components/_AutoLayout.scss +2 -0
  44. package/dist/scss/components/_Avatar.scss +2 -0
  45. package/dist/scss/components/_Badge.scss +2 -0
  46. package/dist/scss/components/_Box.scss +2 -0
  47. package/dist/scss/components/_Breadcrumbs.scss +2 -0
  48. package/dist/scss/components/_Button.scss +2 -0
  49. package/dist/scss/components/_ButtonGroup.scss +2 -0
  50. package/dist/scss/components/_Card.scss +2 -0
  51. package/dist/scss/components/_Carousel.scss +2 -0
  52. package/dist/scss/components/_Center.scss +2 -0
  53. package/dist/scss/components/_ChatBubble.scss +2 -0
  54. package/dist/scss/components/_Checkbox.scss +2 -0
  55. package/dist/scss/components/_Chip.scss +2 -0
  56. package/dist/scss/components/_ChoiceGroup.scss +2 -0
  57. package/dist/scss/components/_Code.scss +2 -0
  58. package/dist/scss/components/_Collapsible.scss +2 -0
  59. package/dist/scss/components/_ColorPicker.scss +2 -0
  60. package/dist/scss/components/_Combobox.scss +2 -0
  61. package/dist/scss/components/_Container.scss +2 -0
  62. package/dist/scss/components/_Countdown.scss +2 -0
  63. package/dist/scss/components/_DateField.scss +2 -0
  64. package/dist/scss/components/_Dialog.scss +2 -0
  65. package/dist/scss/components/_Diff.scss +2 -0
  66. package/dist/scss/components/_Divider.scss +2 -0
  67. package/dist/scss/components/_Dock.scss +2 -0
  68. package/dist/scss/components/_EditableField.scss +2 -0
  69. package/dist/scss/components/_Field.scss +2 -0
  70. package/dist/scss/components/_Fieldset.scss +2 -0
  71. package/dist/scss/components/_FileInput.scss +2 -0
  72. package/dist/scss/components/_Footer.scss +2 -0
  73. package/dist/scss/components/_Grid.scss +2 -0
  74. package/dist/scss/components/_Header.scss +2 -0
  75. package/dist/scss/components/_Heading.scss +2 -0
  76. package/dist/scss/components/_Icon.scss +2 -0
  77. package/dist/scss/components/_IconButton.scss +2 -0
  78. package/dist/scss/components/_Image.scss +2 -0
  79. package/dist/scss/components/_Indicator.scss +2 -0
  80. package/dist/scss/components/_Input.scss +2 -0
  81. package/dist/scss/components/_Key.scss +2 -0
  82. package/dist/scss/components/_Link.scss +2 -0
  83. package/dist/scss/components/_List.scss +2 -0
  84. package/dist/scss/components/_Media.scss +2 -0
  85. package/dist/scss/components/_Menu.scss +2 -0
  86. package/dist/scss/components/_Motion.scss +2 -0
  87. package/dist/scss/components/_NumberField.scss +2 -0
  88. package/dist/scss/components/_NumberStepper.scss +2 -0
  89. package/dist/scss/components/_OtpField.scss +2 -0
  90. package/dist/scss/components/_PageTransition.scss +2 -0
  91. package/dist/scss/components/_Pagination.scss +2 -0
  92. package/dist/scss/components/_Popover.scss +2 -0
  93. package/dist/scss/components/_Progress.scss +2 -0
  94. package/dist/scss/components/_RadialProgress.scss +2 -0
  95. package/dist/scss/components/_Radio.scss +2 -0
  96. package/dist/scss/components/_Rating.scss +2 -0
  97. package/dist/scss/components/_ScrollCarousel.scss +2 -0
  98. package/dist/scss/components/_ScrollSpy.scss +2 -0
  99. package/dist/scss/components/_SegmentedControl.scss +2 -0
  100. package/dist/scss/components/_Select.scss +2 -0
  101. package/dist/scss/components/_Sidebar.scss +2 -0
  102. package/dist/scss/components/_Skeleton.scss +2 -0
  103. package/dist/scss/components/_Slider.scss +2 -0
  104. package/dist/scss/components/_Spacer.scss +2 -0
  105. package/dist/scss/components/_SpeedDial.scss +2 -0
  106. package/dist/scss/components/_Spinner.scss +2 -0
  107. package/dist/scss/components/_Stack.scss +2 -0
  108. package/dist/scss/components/_Stat.scss +2 -0
  109. package/dist/scss/components/_Status.scss +2 -0
  110. package/dist/scss/components/_Steps.scss +2 -0
  111. package/dist/scss/components/_Swap.scss +2 -0
  112. package/dist/scss/components/_Switch.scss +2 -0
  113. package/dist/scss/components/_Table.scss +2 -0
  114. package/dist/scss/components/_Tabs.scss +2 -0
  115. package/dist/scss/components/_Text.scss +2 -0
  116. package/dist/scss/components/_TextRotate.scss +2 -0
  117. package/dist/scss/components/_Textarea.scss +2 -0
  118. package/dist/scss/components/_ThemeProvider.scss +2 -0
  119. package/dist/scss/components/_Tile.scss +2 -0
  120. package/dist/scss/components/_Toast.scss +2 -0
  121. package/dist/scss/components/_ToggleButton.scss +2 -0
  122. package/dist/scss/components/_Tooltip.scss +2 -0
  123. package/dist/scss/components/_Type.scss +2 -0
  124. package/dist/scss/components/_Visibility.scss +2 -0
  125. package/dist/scss/components/_VisuallyHidden.scss +2 -0
  126. package/dist/shapes.d.ts +12 -1
  127. package/dist/shapes.js +11 -10
  128. package/dist/style-manifest.json +18830 -132
  129. package/dist/theme.js +6 -1
  130. package/dist/themeFiles/index.d.ts +9 -0
  131. package/dist/themeFiles/index.js +9 -0
  132. package/dist/themeFiles/scssModule.d.ts +100 -0
  133. package/dist/themeFiles/scssModule.js +384 -0
  134. package/dist/themeFiles/spec.d.ts +198 -0
  135. package/dist/themeFiles/spec.js +877 -0
  136. package/dist/tokens.css +30 -1
  137. package/dist/typography.js +1 -1
  138. package/dist/utilities.css +435 -0
  139. package/dist/utilities.d.ts +8 -0
  140. package/dist/utilities.js +75 -0
  141. package/package.json +20 -1
  142. package/src/components/AutoLayout/AutoLayout.scss +2 -26
  143. package/src/components/Carousel/Carousel.scss +122 -126
  144. package/src/components/Checkbox/Checkbox.scss +2 -2
  145. package/src/components/Chip/Chip.scss +63 -8
  146. package/src/components/ChoiceGroup/ChoiceGroup.scss +19 -1
  147. package/src/components/Collapsible/Collapsible.scss +3 -0
  148. package/src/components/ColorPicker/ColorPicker.scss +95 -0
  149. package/src/components/Combobox/Combobox.scss +4 -2
  150. package/src/components/Dialog/Dialog.scss +24 -7
  151. package/src/components/EditableField/EditableField.scss +275 -0
  152. package/src/components/Grid/Grid.scss +2 -70
  153. package/src/components/Input/Input.scss +29 -0
  154. package/src/components/Menu/Menu.scss +18 -0
  155. package/src/components/NumberField/NumberField.scss +56 -9
  156. package/src/components/NumberStepper/NumberStepper.scss +13 -0
  157. package/src/components/Radio/Radio.scss +2 -2
  158. package/src/components/ScrollCarousel/ScrollCarousel.scss +142 -0
  159. package/src/components/Select/Select.scss +4 -0
  160. package/src/components/Slider/Slider.scss +51 -6
  161. package/src/components/Stack/Stack.scss +2 -48
  162. package/src/components/Switch/Switch.scss +9 -6
  163. package/src/components/Text/Text.scss +5 -2
  164. package/src/styles/_breakpoint-vars.scss +17 -11
  165. package/src/styles/_controls.scss +2 -2
  166. package/src/styles/_core.scss +1 -0
  167. package/src/styles/_kit.scss +483 -56
  168. package/src/styles/_layout-props.scss +197 -0
  169. package/src/styles/_manifest.scss +53 -23
  170. package/src/styles/_mixins.scss +53 -15
  171. package/src/styles/_slides.scss +112 -0
  172. package/src/styles/_utilities.scss +104 -0
  173. package/src/styles/breakpoints.test.ts +21 -1
  174. package/src/styles/entryPoints.test.ts +25 -2
  175. package/src/styles/index.scss +3 -1
  176. package/src/styles/kit.test.ts +171 -1
  177. package/src/styles/public/_breakpoints.scss +43 -0
  178. package/src/styles/public/_slides.scss +3 -0
  179. package/src/styles/public/_utilities.scss +3 -0
  180. package/src/styles/resets.scss +7 -0
  181. package/src/styles/utilities.test.ts +39 -0
  182. package/themes/bevel.css +187 -187
  183. package/themes/bevel.scss +960 -958
  184. package/themes/extrude.css +42 -42
  185. package/themes/folio.css +200 -200
  186. package/themes/folio.scss +880 -878
  187. package/themes/outline.css +18 -18
  188. package/themes/pebble.css +8 -8
  189. package/themes/ridge.css +188 -188
  190. package/themes/ridge.scss +820 -818
  191. package/themes/soft.css +11 -11
  192. package/themes/ticket.css +180 -180
  193. package/themes/ticket.scss +881 -879
  194. package/themes/tilt.css +25 -25
@@ -0,0 +1,275 @@
1
+ @use 'sass:map';
2
+ @use '../../styles/controls';
3
+ @use '../../styles/mixins';
4
+
5
+ // A color picker's swatch paints literal colors, whatever the theme: the
6
+ // checkerboard behind a transparent color reads as "nothing here", not as a
7
+ // surface, so it does not follow the palette.
8
+ /* stylelint-disable color-no-hex */
9
+ $checker: repeating-conic-gradient(#c8ccd2 0% 25%, #fff 0% 50%) 0 0 / 0.5rem 0.5rem;
10
+
11
+ @layer greese.components {
12
+ .greese-EditableField {
13
+ --greese-editablefield-dial: 2.25rem;
14
+
15
+ @include mixins.component-root;
16
+
17
+ display: flex;
18
+ gap: var(--greese-space-2);
19
+ align-items: center;
20
+ min-inline-size: 0;
21
+
22
+ &[data-disabled] {
23
+ opacity: 0.55;
24
+ }
25
+ }
26
+
27
+ // A property row: the name on one side, the control on the other, so a
28
+ // column of fields lines up down the panel.
29
+ .greese-EditableField--row {
30
+ justify-content: space-between;
31
+ }
32
+
33
+ .greese-EditableField--column {
34
+ flex-direction: column;
35
+ align-items: stretch;
36
+ }
37
+
38
+ .greese-EditableField-label {
39
+ flex: none;
40
+ color: var(--greese-color-fg-muted);
41
+
42
+ // The name must not push the control out of a narrow panel.
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ white-space: nowrap;
46
+
47
+ .greese-EditableField--column > & {
48
+ align-self: start;
49
+ }
50
+ }
51
+
52
+ // Every control sits on the other side of the row and takes the slack.
53
+ .greese-EditableField-control {
54
+ flex: 1 1 auto;
55
+ min-inline-size: 0;
56
+ }
57
+
58
+ // A checkbox, switch or dial is its own size: stretching one would leave the
59
+ // hit target floating in a box of empty space.
60
+ .greese-EditableField--checkbox > .greese-EditableField-control,
61
+ .greese-EditableField--switch > .greese-EditableField-control,
62
+ .greese-EditableField--radio > .greese-EditableField-control,
63
+ .greese-EditableField--segmented > .greese-EditableField-control {
64
+ flex: 0 0 auto;
65
+ }
66
+
67
+ .greese-EditableField-number,
68
+ .greese-EditableField-range,
69
+ .greese-EditableField-knob {
70
+ display: flex;
71
+ gap: var(--greese-space-2);
72
+ align-items: center;
73
+ }
74
+
75
+ .greese-EditableField-number > .greese-NumberField,
76
+ .greese-EditableField-range > .greese-Slider,
77
+ .greese-EditableField-knob > .greese-NumberField {
78
+ flex: 1;
79
+ min-inline-size: 0;
80
+ }
81
+
82
+ // The × that empties the field: a small, muted target inside its end.
83
+ .greese-EditableField-clear {
84
+ @include mixins.touch-target;
85
+
86
+ position: relative;
87
+ display: inline-grid;
88
+ flex: none;
89
+ place-items: center;
90
+ inline-size: 1.25rem;
91
+ block-size: 1.25rem;
92
+ padding: 0;
93
+ color: var(--greese-color-fg-muted);
94
+ cursor: pointer;
95
+ background: none;
96
+ border: 0;
97
+ border-radius: var(--greese-corner-round);
98
+
99
+ @include mixins.hover {
100
+ color: var(--greese-color-fg);
101
+ background-color: color-mix(in oklab, var(--greese-color-fg) 8%, transparent);
102
+ }
103
+
104
+ > svg {
105
+ inline-size: 0.75rem;
106
+ block-size: 0.75rem;
107
+ }
108
+ }
109
+
110
+ // A textarea's × sits in its top end corner, over the text's margin.
111
+ .greese-EditableField-textarea {
112
+ position: relative;
113
+
114
+ > .greese-EditableField-clear {
115
+ position: absolute;
116
+ inset-block-start: var(--greese-space-1);
117
+ inset-inline-end: var(--greese-space-1);
118
+ }
119
+
120
+ &:has(> .greese-EditableField-clear) .greese-Textarea-control {
121
+ padding-inline-end: 1.75rem;
122
+ }
123
+ }
124
+
125
+ .greese-EditableField-unit,
126
+ .greese-EditableField-value {
127
+ flex: none;
128
+ font-variant-numeric: tabular-nums;
129
+ color: var(--greese-color-fg-muted);
130
+ }
131
+
132
+ .greese-EditableField-value {
133
+ min-inline-size: 3ch;
134
+ text-align: end;
135
+ }
136
+
137
+ // The color swatch that opens the picker. It takes the slack like a text
138
+ // field does, so the color a field holds is as easy to see and to hit as
139
+ // the value in the row above it. The floor is a hit target: the button has
140
+ // no content, so nothing else would stop it collapsing to a sliver.
141
+ .greese-EditableField-swatch {
142
+ inline-size: 100%;
143
+ padding: 0;
144
+ cursor: pointer;
145
+ background:
146
+ linear-gradient(
147
+ var(--greese-editablefield-swatch),
148
+ var(--greese-editablefield-swatch)
149
+ ),
150
+ $checker;
151
+ border: 0;
152
+ border-radius: var(--greese-corner-inset);
153
+ box-shadow: inset 0 0 0 1px var(--greese-color-border-strong);
154
+
155
+ // As tall as the field's other controls at its size, on every pointer;
156
+ // a coarse pointer gets a 44px hit area around it instead.
157
+ position: relative;
158
+ min-inline-size: var(--greese-editablefield-height, 1.75rem);
159
+ block-size: var(--greese-editablefield-height, 1.75rem);
160
+
161
+ @include mixins.touch-target;
162
+
163
+ &:focus-visible {
164
+ outline: 2px solid var(--greese-color-focus-ring);
165
+ outline-offset: 2px;
166
+ }
167
+
168
+ &:disabled {
169
+ cursor: not-allowed;
170
+ }
171
+
172
+ // No color at all, and a mixed selection, are different states: a slash
173
+ // for the one that is set to nothing, a dim fill for the one that has no
174
+ // single answer yet.
175
+ &[data-none] {
176
+ background:
177
+ linear-gradient(
178
+ to top right,
179
+ transparent calc(50% - 1px),
180
+ var(--greese-color-negative) calc(50% - 1px),
181
+ var(--greese-color-negative) calc(50% + 1px),
182
+ transparent calc(50% + 1px)
183
+ ),
184
+ var(--greese-color-surface);
185
+ }
186
+
187
+ &[data-mixed] {
188
+ background: var(--greese-color-track);
189
+ box-shadow: inset 0 0 0 1px var(--greese-color-border-strong);
190
+ opacity: 0.7;
191
+ }
192
+ }
193
+
194
+ // The picker's popover, and the "No color" button under it.
195
+ // `display` is scoped to the open state on purpose. An author `display`
196
+ // beats the UA's `[popover]:not(:popover-open) { display: none }`, so
197
+ // declaring it unconditionally leaves the closed popover laid out and
198
+ // hit-testable — invisible, because the exit state sets `opacity: 0`, while
199
+ // still swallowing clicks meant for whatever is under it.
200
+ .greese-EditableField-picker:popover-open {
201
+ display: grid;
202
+ gap: var(--greese-space-3);
203
+ }
204
+
205
+ .greese-EditableField-none {
206
+ padding: var(--greese-space-2);
207
+ font: inherit;
208
+ color: var(--greese-color-fg);
209
+ cursor: pointer;
210
+ background: var(--greese-color-surface);
211
+ border: 1px solid var(--greese-color-border);
212
+ border-radius: var(--greese-corner-inset);
213
+
214
+ &:hover {
215
+ background: var(--greese-color-track);
216
+ }
217
+
218
+ &:focus-visible {
219
+ outline: 2px solid var(--greese-color-focus-ring);
220
+ outline-offset: 2px;
221
+ }
222
+ }
223
+
224
+ // The dial: a circle whose dot shows where it points.
225
+ .greese-EditableField-dial {
226
+ position: relative;
227
+ flex: none;
228
+ inline-size: var(--greese-editablefield-dial);
229
+ block-size: var(--greese-editablefield-dial);
230
+ cursor: grab;
231
+ background: var(--greese-color-track);
232
+ border: 1px solid var(--greese-color-border);
233
+ border-radius: var(--greese-corner-round);
234
+
235
+ // Turning the dial must not scroll the page under a finger.
236
+ touch-action: none;
237
+
238
+ &:focus-visible {
239
+ outline: 2px solid var(--greese-color-focus-ring);
240
+ outline-offset: 2px;
241
+ }
242
+
243
+ &[aria-disabled='true'] {
244
+ cursor: not-allowed;
245
+ }
246
+ }
247
+
248
+ .greese-EditableField-dialDot {
249
+ position: absolute;
250
+ inset: 0;
251
+ display: block;
252
+
253
+ // The dot rides at the top of the circle and the whole layer rotates, so
254
+ // one angle drives it with no trigonometry in JavaScript.
255
+ rotate: var(--greese-editablefield-angle, 0deg);
256
+
257
+ &::after {
258
+ position: absolute;
259
+ inset-block-start: 0.125rem;
260
+ inset-inline-start: 50%;
261
+ inline-size: 0.375rem;
262
+ block-size: 0.375rem;
263
+ content: '';
264
+ background: var(--greese-color-primary);
265
+ border-radius: var(--greese-corner-round);
266
+ translate: -50% 0;
267
+ }
268
+ }
269
+
270
+ @each $size, $spec in controls.$sizes {
271
+ .greese-EditableField--size-#{$size} {
272
+ --greese-editablefield-height: #{map.get($spec, height)};
273
+ }
274
+ }
275
+ }
@@ -1,29 +1,4 @@
1
- @use 'sass:map';
2
- @use '../../styles/mixins';
3
-
4
- $column-counts: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
5
-
6
- @function column-tokens() {
7
- $map: ();
8
-
9
- @each $n in $column-counts {
10
- $map: map.set($map, $n, repeat(#{$n}, minmax(0, 1fr)));
11
- }
12
-
13
- @return $map;
14
- }
15
-
16
- @function size-tokens() {
17
- $map: (
18
- full: #{'1 / -1'},
19
- );
20
-
21
- @each $n in $column-counts {
22
- $map: map.set($map, $n, span #{$n} / span #{$n});
23
- }
24
-
25
- @return $map;
26
- }
1
+ @use '../../styles/layout-props';
27
2
 
28
3
  @layer greese.components {
29
4
  // Every Grid can be a cell; `container` makes it a grid too.
@@ -39,48 +14,5 @@ $column-counts: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
39
14
  container-type: inline-size;
40
15
  }
41
16
 
42
- @include mixins.responsive-prop(
43
- 'greese-Grid',
44
- 'columns',
45
- grid-template-columns,
46
- column-tokens()
47
- );
48
-
49
- // After `columns`, so it wins where both apply.
50
- @include mixins.responsive-prop(
51
- 'greese-Grid',
52
- 'min-child',
53
- grid-template-columns,
54
- $template: 'repeat(auto-fill, minmax(min(VALUE, 100%), 1fr))'
55
- );
56
- @include mixins.responsive-prop('greese-Grid', 'gap', gap, mixins.space-tokens());
57
- @include mixins.responsive-prop(
58
- 'greese-Grid',
59
- 'align',
60
- align-items,
61
- (
62
- start: start,
63
- center: center,
64
- end: end,
65
- stretch: stretch,
66
- )
67
- );
68
- @include mixins.responsive-prop(
69
- 'greese-Grid',
70
- 'justify',
71
- justify-items,
72
- (
73
- start: start,
74
- center: center,
75
- end: end,
76
- stretch: stretch,
77
- )
78
- );
79
- @include mixins.responsive-prop(
80
- 'greese-Grid',
81
- 'size',
82
- grid-column,
83
- size-tokens(),
84
- $container-scope: '.greese-Grid--queries >'
85
- );
17
+ @include layout-props.grid;
86
18
  }
@@ -8,7 +8,16 @@
8
8
  display: flex;
9
9
  gap: var(--greese-space-2);
10
10
  align-items: center;
11
+
12
+ // Fills whatever it sits in — a Field, a table cell, a flex row.
11
13
  inline-size: 100%;
14
+
15
+ // …but imposes no floor of its own. A flex item's automatic minimum size is
16
+ // its content's min-content width, and an `<input>`'s min-content width is
17
+ // whatever `size` says — about twenty characters by default, a number
18
+ // nobody chose. That is what stopped a text field from ever being narrower
19
+ // than twenty characters, however little the layout had to give it.
20
+ min-inline-size: 0;
12
21
  min-block-size: var(--greese-input-height);
13
22
  padding-inline: var(--greese-input-padding);
14
23
  cursor: text;
@@ -47,6 +56,26 @@
47
56
  }
48
57
  }
49
58
 
59
+ // `htmlSize`: as wide as that many characters and no wider, so the frame
60
+ // hugs the input rather than stretching or shrinking. The doubled class
61
+ // beats a container's `flex: 1 1 auto` (EditableField's control slot, a
62
+ // toolbar row) without depending on which stylesheet the bundle happens to
63
+ // emit last.
64
+ .greese-Input.greese-Input--measured {
65
+ inline-size: fit-content;
66
+
67
+ // Neither grows nor gives up its measure in a crowded row.
68
+ flex: none;
69
+
70
+ // `size` alone measures in the font's average character, narrower than
71
+ // its digits, so seven of them overflow `size="7"`. `--greese-input-size`
72
+ // (set by the React component) measures in `ch`; without it, `size` rules.
73
+ .greese-Input-control {
74
+ flex: none;
75
+ inline-size: calc(var(--greese-input-size) * 1ch);
76
+ }
77
+ }
78
+
50
79
  @each $size, $spec in controls.$sizes {
51
80
  .greese-Input--size-#{$size} {
52
81
  --greese-input-height: #{map.get($spec, height)};
@@ -20,6 +20,24 @@
20
20
  }
21
21
  }
22
22
 
23
+ // A context menu is a Menu surface with a different anchor, so it carries
24
+ // `greese-Menu` too and changes only what the anchor implies.
25
+ .greese-ContextMenu {
26
+ // Menu's width floor makes a dropdown look deliberate under the button
27
+ // that opened it. A context menu has no button: it appears at the pointer,
28
+ // so it should be exactly as wide as its own items.
29
+ min-inline-size: 0;
30
+ }
31
+
32
+ // The stand-in for the pointer position: a zero-size box a ContextMenu
33
+ // anchors to. Inert and never painted — only its position matters.
34
+ .greese-ContextMenu-point {
35
+ position: fixed;
36
+ inline-size: 0;
37
+ block-size: 0;
38
+ pointer-events: none;
39
+ }
40
+
23
41
  .greese-Menu-viewport {
24
42
  min-block-size: 0;
25
43
  padding: var(--greese-space-1);
@@ -5,10 +5,13 @@
5
5
  font-variant-numeric: tabular-nums;
6
6
  }
7
7
 
8
+ // Two carets in a column, the height of the field between them. Side by side
9
+ // they cost about 3.5rem of a field's width, which is most of a row in a
10
+ // dense panel; stacked they cost one button's width.
8
11
  .greese-NumberField-steppers {
9
12
  display: inline-flex;
10
- gap: 2px;
11
- margin-inline-end: calc(-1 * var(--greese-space-2));
13
+ flex-direction: column;
14
+ margin-inline-end: calc(-1 * var(--greese-space-1));
12
15
  }
13
16
 
14
17
  .greese-NumberField-step {
@@ -17,8 +20,6 @@
17
20
  position: relative;
18
21
  display: inline-grid;
19
22
  place-items: center;
20
- inline-size: 1.75rem;
21
- block-size: 1.75rem;
22
23
  padding: 0;
23
24
  color: var(--greese-color-fg-muted);
24
25
  cursor: pointer;
@@ -26,11 +27,6 @@
26
27
  border: 0;
27
28
  border-radius: var(--greese-corner-inset);
28
29
 
29
- > svg {
30
- inline-size: 1rem;
31
- block-size: 1rem;
32
- }
33
-
34
30
  @include mixins.hover {
35
31
  color: var(--greese-color-fg);
36
32
  background-color: color-mix(in oklab, var(--greese-color-fg) 8%, transparent);
@@ -41,4 +37,55 @@
41
37
  opacity: 0.4;
42
38
  }
43
39
  }
40
+
41
+ // Stacked: each caret takes half the row, so the pair is one button tall.
42
+ .greese-NumberField-steppers .greese-NumberField-step {
43
+ inline-size: 1.25rem;
44
+ block-size: 0.875rem;
45
+
46
+ > svg {
47
+ inline-size: 0.75rem;
48
+ block-size: 0.75rem;
49
+ }
50
+ }
51
+
52
+ // Split: a minus at one end of the field and a plus at the other, each a
53
+ // full-height target.
54
+ .greese-NumberField--split .greese-NumberField-step {
55
+ inline-size: 1.5rem;
56
+ block-size: 1.5rem;
57
+
58
+ > svg {
59
+ inline-size: 0.875rem;
60
+ block-size: 0.875rem;
61
+ }
62
+ }
63
+
64
+ // The × that empties the field: a small, muted target inside its end.
65
+ .greese-NumberField-clear {
66
+ @include mixins.touch-target;
67
+
68
+ position: relative;
69
+ display: inline-grid;
70
+ flex: none;
71
+ place-items: center;
72
+ inline-size: 1.25rem;
73
+ block-size: 1.25rem;
74
+ padding: 0;
75
+ color: var(--greese-color-fg-muted);
76
+ cursor: pointer;
77
+ background: none;
78
+ border: 0;
79
+ border-radius: var(--greese-corner-round);
80
+
81
+ @include mixins.hover {
82
+ color: var(--greese-color-fg);
83
+ background-color: color-mix(in oklab, var(--greese-color-fg) 8%, transparent);
84
+ }
85
+
86
+ > svg {
87
+ inline-size: 0.75rem;
88
+ block-size: 0.75rem;
89
+ }
90
+ }
44
91
  }
@@ -0,0 +1,13 @@
1
+ @layer greese.components {
2
+ // A minus at one end and a plus at the other, with the value between them.
3
+ // The field keeps its own frame; only what sits inside it differs from a
4
+ // NumberField.
5
+ .greese-NumberStepper {
6
+ --greese-numberstepper-gap: var(--greese-space-1);
7
+ }
8
+
9
+ // The value is centred because it reads as a quantity, not as typed text.
10
+ .greese-NumberStepper .greese-Input-control {
11
+ text-align: center;
12
+ }
13
+ }
@@ -5,8 +5,8 @@
5
5
  @include mixins.touch-target;
6
6
 
7
7
  position: relative;
8
- inline-size: 1.125rem;
9
- block-size: 1.125rem;
8
+ inline-size: var(--greese-choice-box, 1.125rem);
9
+ block-size: var(--greese-choice-box, 1.125rem);
10
10
  margin: 0;
11
11
  cursor: pointer;
12
12
  background-color: var(--greese-color-canvas);
@@ -0,0 +1,142 @@
1
+ @use '../../styles/mixins';
2
+ @use '../../styles/slides';
3
+
4
+ @layer greese.components {
5
+ .greese-ScrollCarousel {
6
+ @include mixins.component-root;
7
+
8
+ position: relative;
9
+ display: grid;
10
+ gap: var(--greese-space-3);
11
+ min-inline-size: 0;
12
+ }
13
+
14
+ // Native scrolling and snapping: swipe, trackpad and keys all work.
15
+ .greese-ScrollCarousel-viewport {
16
+ --greese-scrollcarousel-per-view: 1;
17
+ --greese-scrollcarousel-gap: var(--greese-space-3);
18
+
19
+ display: flex;
20
+ gap: var(--greese-scrollcarousel-gap);
21
+ overflow-x: auto;
22
+ overscroll-behavior-x: contain;
23
+ scroll-snap-type: x mandatory;
24
+ scrollbar-width: none;
25
+ border-radius: var(--greese-corner-panel);
26
+
27
+ &::-webkit-scrollbar {
28
+ display: none;
29
+ }
30
+
31
+ @include mixins.focus-ring;
32
+
33
+ // The buttons and dots read this to choose a smooth or instant scroll.
34
+ @include mixins.motion-safe {
35
+ scroll-behavior: smooth;
36
+ }
37
+ }
38
+
39
+ // Vertical: a column in a viewport of its own height, snapping on y.
40
+ .greese-ScrollCarousel--vertical > .greese-ScrollCarousel-viewport {
41
+ flex-direction: column;
42
+ block-size: var(--greese-scrollcarousel-size, 20rem);
43
+ overflow: hidden auto;
44
+ overscroll-behavior: auto contain;
45
+ scroll-snap-type: y mandatory;
46
+ }
47
+
48
+ // Each slide's width: the viewport shared by `per-view` slides and the gaps.
49
+ .greese-ScrollCarouselSlide {
50
+ --greese-scrollcarousel-gaps: calc(
51
+ (var(--greese-scrollcarousel-per-view) - 1) * var(--greese-scrollcarousel-gap)
52
+ );
53
+
54
+ flex: 0 0
55
+ calc(
56
+ (100% - var(--greese-scrollcarousel-gaps)) / var(--greese-scrollcarousel-per-view)
57
+ );
58
+ min-inline-size: 0;
59
+ scroll-snap-align: start;
60
+ }
61
+
62
+ @include slides.controls('greese-ScrollCarousel', scrollcarousel);
63
+
64
+ // Vertical: the buttons sit centred on the top and bottom edges, the
65
+ // chevron turned to point up and down.
66
+ .greese-ScrollCarousel--vertical > .greese-ScrollCarousel-control {
67
+ inset-inline: 0;
68
+ margin-inline: auto;
69
+ translate: none;
70
+
71
+ > svg {
72
+ rotate: 90deg;
73
+ scale: 1;
74
+ }
75
+ }
76
+
77
+ .greese-ScrollCarousel--vertical > .greese-ScrollCarousel-control--previous {
78
+ inset-block-start: var(--greese-space-2);
79
+ }
80
+
81
+ .greese-ScrollCarousel--vertical > .greese-ScrollCarousel-control--next {
82
+ inset-block: auto calc(var(--greese-space-2) + var(--greese-scrollcarousel-dots, 0px));
83
+
84
+ > svg {
85
+ rotate: -90deg;
86
+ }
87
+ }
88
+
89
+ // A strip of controls (tabs, a toolbar): each as wide as its content, no
90
+ // snapping, and small buttons over the ends that scroll it a view along.
91
+ .greese-ScrollCarousel--strip {
92
+ gap: 0;
93
+
94
+ > .greese-ScrollCarousel-viewport {
95
+ --greese-scrollcarousel-gap: var(--greese-space-1);
96
+
97
+ align-items: center;
98
+ scroll-snap-type: none;
99
+ border-radius: 0;
100
+ }
101
+
102
+ > .greese-ScrollCarousel-control {
103
+ inset-block-start: 50%;
104
+ inline-size: 1.5rem;
105
+ block-size: 1.5rem;
106
+ box-shadow: var(--greese-shadow-sm);
107
+
108
+ > svg {
109
+ inline-size: 0.875rem;
110
+ block-size: 0.875rem;
111
+ }
112
+ }
113
+
114
+ > .greese-ScrollCarousel-control--previous {
115
+ inset-inline-start: 0;
116
+ }
117
+
118
+ > .greese-ScrollCarousel-control--next {
119
+ inset-inline-end: 0;
120
+ }
121
+ }
122
+
123
+ .greese-ScrollCarousel--strip
124
+ > .greese-ScrollCarousel-viewport
125
+ > .greese-ScrollCarouselSlide {
126
+ flex: none;
127
+ scroll-snap-align: none;
128
+ }
129
+
130
+ // Phones swipe; the buttons come back where a pointer can hover.
131
+ @media (hover: none) {
132
+ .greese-ScrollCarousel-control {
133
+ display: none;
134
+ }
135
+ }
136
+
137
+ @include mixins.responsive-prop(
138
+ 'greese-ScrollCarousel',
139
+ 'per-view',
140
+ --greese-scrollcarousel-per-view
141
+ );
142
+ }
@@ -17,6 +17,10 @@
17
17
  padding-inline: var(--greese-select-padding)
18
18
  calc(var(--greese-select-padding) + 1.5em);
19
19
  font: inherit;
20
+
21
+ // A line no taller than the text, so the frame's height is the size's on
22
+ // every pointer, not the font's default line.
23
+ line-height: 1.25;
20
24
  color: inherit;
21
25
  cursor: pointer;
22
26
  background: none;