@smwb/ui-styles 1.5.1 → 2.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.
- package/README.md +210 -12
- package/less/components/appBar/appBar.less +108 -0
- package/less/components/avatar/avatar.less +22 -0
- package/less/components/button/button.less +7 -1
- package/less/components/card/card.less +15 -4
- package/less/components/chart/chart.less +16 -15
- package/less/components/checkbox/checkbox.less +55 -4
- package/less/components/chip/chip.less +48 -3
- package/less/components/commandPalette/commandPalette.entry.less +2 -0
- package/less/components/commandPalette/commandPalette.less +233 -0
- package/less/components/composer/composer.entry.less +2 -0
- package/less/components/composer/composer.less +294 -0
- package/less/components/composer/composer.variables.less +10 -0
- package/less/components/dagView/dagView.entry.less +2 -0
- package/less/components/dagView/dagView.less +280 -0
- package/less/components/descriptionList/descriptionList.less +7 -1
- package/less/components/diffStat/diffStat.entry.less +2 -0
- package/less/components/diffStat/diffStat.less +76 -0
- package/less/components/kbd/kbd.entry.less +2 -0
- package/less/components/kbd/kbd.less +55 -0
- package/less/components/list/list.less +39 -0
- package/less/components/menu/menu.less +25 -1
- package/less/components/personChip/personChip.less +4 -2
- package/less/components/qrCode/qrCode.entry.less +2 -0
- package/less/components/qrCode/qrCode.less +35 -0
- package/less/components/radioButton/radioButton.less +46 -1
- package/less/components/rating/rating.less +15 -2
- package/less/components/selectField/selectField.less +87 -5
- package/less/components/sidebar/sidebar.less +26 -0
- package/less/components/statusDot/statusDot.entry.less +2 -0
- package/less/components/statusDot/statusDot.less +118 -0
- package/less/components/swimlanes/swimlanes.entry.less +2 -0
- package/less/components/swimlanes/swimlanes.less +126 -0
- package/less/components/tabGroups/tabGroups.entry.less +2 -0
- package/less/components/tabGroups/tabGroups.less +219 -0
- package/less/components/table/table.less +12 -0
- package/less/components/tabs/tab/tab.less +128 -0
- package/less/components/tabs/tabPanel/tabPanel.entry.less +2 -0
- package/less/components/tabs/tabPanel/tabPanel.less +8 -0
- package/less/components/tabs/tabs.less +26 -0
- package/less/components/textField/textField.less +34 -9
- package/less/components/thumbCard/thumbCard.less +12 -2
- package/less/components/toggle/toggle.less +42 -2
- package/less/components/treeView/treeView.less +40 -2
- package/less/components/typography/typography.less +13 -6
- package/less/components.less +10 -0
- package/less/connects/rt/table.less +7 -0
- package/less/default/globals/default.variables.less +152 -1
- package/less/foundation/sizing.less +41 -5
- package/less/foundation/typography.less +21 -0
- package/less/themes/material/variables.less +1 -0
- package/less/themes/summer/variables.less +1 -0
- package/less/variables.less +5 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -19,18 +19,18 @@ npm install @smwb/ui-styles
|
|
|
19
19
|
|
|
20
20
|
## What's inside
|
|
21
21
|
|
|
22
|
-
| Path
|
|
23
|
-
|
|
|
24
|
-
| `less/components.less`
|
|
25
|
-
| `less/foundation/*`
|
|
26
|
-
| `less/default/globals/default.variables.less` | Semantic colors + per-component LESS variables
|
|
27
|
-
| `less/variables.less`
|
|
28
|
-
| `less/components/<name>/<name>.entry.less`
|
|
29
|
-
| `less/tokens.entry.less`
|
|
30
|
-
| `less/headless-base.less`
|
|
31
|
-
| `less/connects/rt/table.entry.less`
|
|
32
|
-
| `less/themes/*`
|
|
33
|
-
| `normalize.css`
|
|
22
|
+
| Path | Purpose |
|
|
23
|
+
| --------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
24
|
+
| `less/components.less` | Aggregate entry — `@import`s foundation + every component |
|
|
25
|
+
| `less/foundation/*` | Tokens: palette, spacing, radius, elevation, breakpoints, sizing |
|
|
26
|
+
| `less/default/globals/default.variables.less` | Semantic colors + per-component LESS variables |
|
|
27
|
+
| `less/variables.less` | Declares the `--smui-*` tokens and emits them on `:root` (light) + `[data-theme="dark"]` |
|
|
28
|
+
| `less/components/<name>/<name>.entry.less` | Per-component entry (component rules only, no tokens) |
|
|
29
|
+
| `less/tokens.entry.less` | The `--smui-*` tokens at zero specificity (`:where(:root)`) for per-component CSS |
|
|
30
|
+
| `less/headless-base.less` | Foundation + theme variables only (no component rules) |
|
|
31
|
+
| `less/connects/rt/table.entry.less` | The React TanStack Table adapter's own rules (column resizer) |
|
|
32
|
+
| `less/themes/*` | Built-in theme packs (`summer`, `material`) |
|
|
33
|
+
| `normalize.css` | Optional CSS reset |
|
|
34
34
|
|
|
35
35
|
## Usage
|
|
36
36
|
|
|
@@ -57,6 +57,204 @@ Override tokens at compile time via a `theme.config` (imported last, so your val
|
|
|
57
57
|
runtime via the `--smui-*` CSS variables. Exports: `./components.less`, `./headless-base.less`,
|
|
58
58
|
`./less/*`, `./normalize.css`.
|
|
59
59
|
|
|
60
|
+
#### Typography: letter case and spacing
|
|
61
|
+
|
|
62
|
+
Every `<Typography>` variant has a Less variable for its size, weight and line height
|
|
63
|
+
(`@smui-font-<variant>-size`, `-weight`, `-lh`) and, in addition, for its letter case and spacing:
|
|
64
|
+
`@smui-font-<variant>-transform` and `@smui-font-<variant>-letter-spacing`. The variants are `h1`–`h6`,
|
|
65
|
+
`subtitle1`, `subtitle2`, `body1`, `body2`, `caption`, `overline` and `button`. The defaults are `auto`, a
|
|
66
|
+
marker meaning the variant emits no `text-transform` or `letter-spacing` at all (as before, so consumer rules on
|
|
67
|
+
the element keep winning), except `overline` and `button`, which stay `uppercase` with `0.06em`. Set them in `theme.config`:
|
|
68
|
+
|
|
69
|
+
```less
|
|
70
|
+
// theme.config: bold uppercase headings with wide tracking
|
|
71
|
+
@smui-font-h1-transform: uppercase;
|
|
72
|
+
@smui-font-h1-letter-spacing: 0.12em;
|
|
73
|
+
@smui-font-h1-weight: 700;
|
|
74
|
+
@smui-font-h2-transform: uppercase;
|
|
75
|
+
@smui-font-h2-letter-spacing: 0.06em;
|
|
76
|
+
@smui-font-h2-weight: 700;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A value that is set compiles to `text-transform: var(--smui-font-<variant>-transform, <value>)` (and the same for
|
|
80
|
+
`letter-spacing`), so it can also be changed at runtime through the `--smui-font-<variant>-transform` and
|
|
81
|
+
`--smui-font-<variant>-letter-spacing` custom properties; the token is not defined by the library, and a
|
|
82
|
+
variant left at `auto` emits neither the declaration nor the token.
|
|
83
|
+
|
|
84
|
+
#### Chart series colours
|
|
85
|
+
|
|
86
|
+
Chart series colours are theme variables too: `@chartSeries1` … `@chartSeries12` (light) and
|
|
87
|
+
`@chartSeries1Dark` … `@chartSeries12Dark` (dark) in `theme.config`, e.g.
|
|
88
|
+
`@chartSeries1: #e4002b; @chartSeries1Dark: #ff6b81;`. They become `--smwb-chart-series-1` … `-12` on
|
|
89
|
+
`.smwb-chart`, which the chart reads and redraws from on a theme switch; a colour in the chart's own
|
|
90
|
+
`option.color` still wins. The defaults are the Okabe–Ito set (7 colours; slots 8–12 are `false` = not
|
|
91
|
+
emitted). Fill slots in order: an unset slot is skipped, so the colours after it shift up (slot 9 without slot 8
|
|
92
|
+
is drawn for the 8th series). Light and dark are separate sets, so set both when the brand colour must
|
|
93
|
+
show in the dark theme. At runtime, set `--smwb-chart-series-N` on the chart itself (its `style`): the chart declares the
|
|
94
|
+
tokens on its own root, so a value set on `:root` or a wrapper does not reach it.
|
|
95
|
+
|
|
96
|
+
#### StatusDot colours
|
|
97
|
+
|
|
98
|
+
`StatusDot` reads only runtime tokens, so the dark theme follows without extra settings: `active` takes
|
|
99
|
+
`--smui-infoColor`, `attention` `--smui-warningColor`, `idle` `--smui-onSurfaceVariant`, `done`
|
|
100
|
+
`--smui-successColor`, `error` `--smui-errorColor` and `unknown` `--smui-outlineColor` (as a ring); `color` takes
|
|
101
|
+
the matching `--smui-*Color` token (`default` is `--smui-onSurfaceVariant`). Its sizes are `@statusDotSizeSm`
|
|
102
|
+
(8px), `@statusDotSizeMd` (12px) and `@statusDotRing` (2px, the ring of `unknown`) in `theme.config`.
|
|
103
|
+
|
|
104
|
+
Against `--smui-surfaceBackground` and `--smui-pageBackground`, the default theme's status colours measure
|
|
105
|
+
at least 3:1 in both modes (the lowest is `attention` on the light surface, 3.04:1). The semantic colours are one
|
|
106
|
+
value for both modes, so a theme that makes `@warningColor` lighter, or any of them darker for the dark theme,
|
|
107
|
+
changes the numbers: re-check them there. `color="secondary"` is the one name that falls short in the default dark
|
|
108
|
+
theme (2.65:1): use a status for state.
|
|
109
|
+
|
|
110
|
+
#### QrCode colours
|
|
111
|
+
|
|
112
|
+
`QrCode` never reads theme tokens for the code itself: the modules are always `@qrCodeDarkColor`
|
|
113
|
+
(`rgb(0 0 0)`) on `@qrCodeLightColor` (`rgb(255 255 255)`), including the quiet zone, so a phone camera
|
|
114
|
+
reads the code in the dark theme, under forced colours and in print (`forced-color-adjust: none` and
|
|
115
|
+
`print-color-adjust: exact` are set on the svg, and the same colours are written as SVG `fill` attributes,
|
|
116
|
+
so a copied or printed svg scans without the library CSS). Only the caption is themed
|
|
117
|
+
(`--smui-onSurfaceVariant`). The corner rounding is `@qrCodeRadius` (`@smui-radius-sm`); the svg clips to
|
|
118
|
+
it, and the quiet zone keeps the modules clear of the rounding.
|
|
119
|
+
|
|
120
|
+
#### DagView colours
|
|
121
|
+
|
|
122
|
+
The dependency graph (`DagView` in `@smwb/ui-solid` and `@smwb/ui-react`) takes its colours from theme variables that default to the
|
|
123
|
+
runtime tokens, so the dark theme follows without extra settings:
|
|
124
|
+
|
|
125
|
+
| Variable (`theme.config`) | Runtime property on `.smwb-dag-view` | Default |
|
|
126
|
+
| ---------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------- |
|
|
127
|
+
| `@dagViewNodeBg` | `--smwb-dag-view-node-bg` | `var(--smui-surfaceBackground)` |
|
|
128
|
+
| `@dagViewNodeBorder` | `--smwb-dag-view-node-border` | `var(--smui-outlineColor)` |
|
|
129
|
+
| `@dagViewEdgeColor` | `--smwb-dag-view-edge-color` | `var(--smui-outlineColor)` |
|
|
130
|
+
| `@dagViewEdgeActiveColor` | `--smwb-dag-view-edge-active-color` | `var(--smui-primaryColor)` |
|
|
131
|
+
| `@dagViewSelectedRing` | `--smwb-dag-view-selected-ring` | `var(--smui-primaryColor)` |
|
|
132
|
+
| `@dagViewStatusIdle` … `@dagViewStatusSkipped` | `--smwb-dag-view-status-idle` … `-skipped` | outline, on-surface variant, info, success, warning, error, disabled text |
|
|
133
|
+
| `@dagViewDimOpacity` | `--smwb-dag-view-dim-opacity` | `0.35` |
|
|
134
|
+
| `@dagViewHeight` | — (the graph's `height`) | `480px` |
|
|
135
|
+
|
|
136
|
+
The statuses are `idle`, `pending`, `running`, `success`, `warning`, `error` and `skipped`; a node with any other
|
|
137
|
+
status is drawn as `idle`. Each colour variable has a `…Dark` twin (e.g. `@dagViewStatusSuccessDark`), `false` by
|
|
138
|
+
default: set it to give the dark theme its own value. At runtime, set the properties on the graph itself, since it
|
|
139
|
+
declares them on its own root: in its `style`, or with a class in a selector that outranks `.smwb-dag-view`, such as
|
|
140
|
+
`.smwb-dag-view.my-graph` (a plain `.my-graph` ties with it, and the graph's stylesheet may load after yours). A
|
|
141
|
+
node's accent comes from `--smwb-dag-view-accent`. For a status of your own, set it on the node and keep the node's
|
|
142
|
+
`accent` prop in front, as the built-in statuses do:
|
|
143
|
+
`.smwb-dag-view__node[data-status="queued"] { --smwb-dag-view-accent: var(--smwb-dag-view-node-accent, teal) }`.
|
|
144
|
+
The graph's default height, `@dagViewHeight`, is set by the stylesheet, so its `height` prop or a height in its
|
|
145
|
+
`style` wins over it.
|
|
146
|
+
|
|
147
|
+
#### Swimlanes
|
|
148
|
+
|
|
149
|
+
`Swimlanes` (in `@smwb/ui-react` and `@smwb/ui-solid`) reads only runtime tokens, so the dark theme follows without
|
|
150
|
+
extra settings: the grid's surface and the sticky header's opaque background come from `--smui-surfaceBackground`,
|
|
151
|
+
the row divider from `--smui-dividerColor`, the hover tint from `--smui-onSurfaceHover`, the selected tint, the
|
|
152
|
+
selection stripe and the focus ring from `--smui-primaryColor`, and a disabled lane's text from
|
|
153
|
+
`--smui-disabledTextColor`.
|
|
154
|
+
|
|
155
|
+
| Variable (`theme.config`) | Runtime property on `.smwb-swimlanes` | Default |
|
|
156
|
+
| ------------------------- | ----------------------------------------------- | ------- |
|
|
157
|
+
| `@swimlanesHeaderWidth` | the fallback of `--smwb-swimlanes-header-width` | `240px` |
|
|
158
|
+
| `@swimlanesHeight` | — (the grid's `height`) | `480px` |
|
|
159
|
+
|
|
160
|
+
Three custom properties drive the layout and the component writes them on its own root:
|
|
161
|
+
`--smwb-swimlanes-row-height` (always, from `rowHeight`), `--smwb-swimlanes-header-width` (only with `headerWidth`)
|
|
162
|
+
and `--smwb-swimlanes-content-min-width` (only with `contentMinWidth`). The last two are yours to set, through the
|
|
163
|
+
props or at runtime on the grid itself (its `style`, which is merged last and wins). The row height is not: the
|
|
164
|
+
virtual window's maths needs it in JS, so it goes through `rowHeight` alone, and a different value in `style` would
|
|
165
|
+
resize the rows in CSS while the window still counts with `rowHeight`, leaving gaps or overlaps. A row's state is one
|
|
166
|
+
internal property, `--smwb-swimlanes-tint`, set on each row and read by both cells so the lane reads as one band:
|
|
167
|
+
`transparent`, `--smui-onSurfaceHover` on hover, and 12% of `--smui-primaryColor` when selected. It is declared on
|
|
168
|
+
the row, so a value set on the grid never reaches the cells.
|
|
169
|
+
|
|
170
|
+
`@swimlanesHeight` is set by the stylesheet, so the `height` prop or a height in `style` wins over it. A selected
|
|
171
|
+
lane also gets a 3px primary stripe on its header, so the selection is not shown by tint alone; forced colours draw
|
|
172
|
+
it as a `Highlight` outline on both cells instead. Cells clip, so content that needs more room needs a wider
|
|
173
|
+
`contentMinWidth`.
|
|
174
|
+
|
|
175
|
+
#### Composer
|
|
176
|
+
|
|
177
|
+
`Composer` is the task input: a textarea that grows from `minRows` to `maxRows`, an optional attach button, a
|
|
178
|
+
`toolbar` slot, a character counter and a send button that turns into Stop while the agent is busy. Its stylesheet is
|
|
179
|
+
`components/composer/composer.less` (per-component entry: `composer.entry.less`). The root's children are flat, and CSS
|
|
180
|
+
grid areas place them for each layout; the spacing is margins, so an absent child leaves no gap.
|
|
181
|
+
|
|
182
|
+
| Class | Element |
|
|
183
|
+
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
184
|
+
| `.smwb-composer` | the root; `_stacked` (field on top, a bar under it: attach, toolbar, counter, action) or `_inline` (one row: attach, field, counter, action; the toolbar, if any, under it) |
|
|
185
|
+
| `_focused`, `_disabled`, `_drag-active`, `_over-limit`, `_busy` | root states; `_focused` follows the textarea's focus, not `:focus-within` |
|
|
186
|
+
| `.smwb-composer__attachments`, `__toolbar` | slots above the field and in the bar |
|
|
187
|
+
| `.smwb-composer__field` | the textarea's wrapper; its `data-value` mirrors the text, and `--smwb-composer-min-rows` / `--smwb-composer-max-rows` set the height range |
|
|
188
|
+
| `.smwb-composer__input` | the textarea |
|
|
189
|
+
| `.smwb-composer__icon-button` | a round native button; with `__attach` and `__action` (`__action_stop` while busy) |
|
|
190
|
+
| `.smwb-composer__counter` (`_error`) | the `n / max` text |
|
|
191
|
+
| `.smwb-composer__status` | the visually hidden live region for the counter |
|
|
192
|
+
|
|
193
|
+
The textarea grows in CSS only: `.smwb-composer__field::after` repeats the text (`content: attr(data-value)`) in a
|
|
194
|
+
hidden box that shares the textarea's font, padding and line height, and the textarea fills the grid cell that box
|
|
195
|
+
sizes, up to `max-height`. Nothing is measured, so the height is right in server-rendered markup and follows a width
|
|
196
|
+
change. The send button is not natively disabled while it is unavailable: `aria-disabled="true"` mutes it, so it keeps
|
|
197
|
+
focus.
|
|
198
|
+
|
|
199
|
+
The colours are theme variables that default to the runtime tokens, so the dark theme follows without extra settings:
|
|
200
|
+
|
|
201
|
+
| Variable (`theme.config`) | Default |
|
|
202
|
+
| -------------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
203
|
+
| `@composerBg`, `@composerBorder`, `@composerBorderFocus` | `var(--smui-surfaceBackground)`, `var(--smui-outlineColor)`, `var(--smui-primaryColor)` |
|
|
204
|
+
| `@composerText`, `@composerPlaceholder` | `var(--smui-onSurfaceColor)`, `var(--smui-onSurfaceVariant)` |
|
|
205
|
+
| `@composerCounter`, `@composerCounterError` | `var(--smui-onSurfaceVariant)`, `var(--smui-errorColor)` |
|
|
206
|
+
| `@composerActionBg`, `@composerActionColor` | `var(--smui-primaryColor)`, `var(--smui-onPrimaryColor)` |
|
|
207
|
+
| `@composerStopBg`, `@composerStopColor` | `var(--smui-onSurfaceColor)`, `var(--smui-surfaceBackground)` |
|
|
208
|
+
| `@composerDragBg` | `var(--smui-primaryContainer)` |
|
|
209
|
+
|
|
210
|
+
Sizes live in `composer.variables.less`: `@composerPadding`, `@composerGap`, `@composerRadius`, `@composerLineHeight`
|
|
211
|
+
(one text line, `1.5rem`), `@composerFieldPaddingY` / `@composerFieldPaddingX` and `@composerIconButtonSize`.
|
|
212
|
+
|
|
213
|
+
#### DiffStat colours
|
|
214
|
+
|
|
215
|
+
`DiffStat` draws its figures and cells in the theme's `--smui-successColor` and `--smui-errorColor`, each mixed toward
|
|
216
|
+
`--smui-onSurfaceColor` so that the text keeps 4.5:1 contrast (the raw tokens measure about 3.9–4.3:1) and a cell
|
|
217
|
+
keeps 3:1 against a neutral one, in the light and the dark theme alike. A cell nobody owns is an outline of the surface ink
|
|
218
|
+
(60% of it, 3.69:1 or better against the page and the surface in every bundled theme, light and dark), not a fill:
|
|
219
|
+
a fill light enough to sit quietly measured 1.08–1.29:1 against a light page and made "4 of 5" look like "5 of 5":
|
|
220
|
+
|
|
221
|
+
| Variable (`theme.config`) | Runtime property on `.smwb-diff-stat` | Default |
|
|
222
|
+
| ------------------------- | ------------------------------------- | --------------------------------------------------------------- |
|
|
223
|
+
| `@diffStatColorMix` | — (compiled into the two below) | `55%` of the success or error token |
|
|
224
|
+
| — | `--smwb-diff-stat-added` | success token mixed toward the surface ink |
|
|
225
|
+
| — | `--smwb-diff-stat-removed` | error token mixed toward the surface ink |
|
|
226
|
+
| `@diffStatNeutralMix` | `--smwb-diff-stat-neutral` | the surface ink at `60%`, the outline colour of an unowned cell |
|
|
227
|
+
|
|
228
|
+
Sizes are `@diffStatFontSize`, `@diffStatCompactFontSize`, `@diffStatCellWidth`, `@diffStatCellHeight`,
|
|
229
|
+
`@diffStatCompactCellHeight`, `@diffStatCellGap` and `@diffStatCellRadius`. At runtime, set the three properties on the
|
|
230
|
+
`DiffStat` itself (its `style`, or a class that outranks `.smwb-diff-stat`).
|
|
231
|
+
|
|
232
|
+
#### AppBar window controls
|
|
233
|
+
|
|
234
|
+
`AppBar variant="window"` is the title bar of a desktop app window (Electron, Tauri, an installed PWA with Window
|
|
235
|
+
Controls Overlay): the bar drags the window (`-webkit-app-region: drag`) and every control inside it is taken out of
|
|
236
|
+
the drag region. The width reserved for the OS window buttons is `@appBarWindowControlsLeft` (64px, the macOS traffic
|
|
237
|
+
lights) and `@appBarWindowControlsRight` (138px, three 46px Windows/Linux caption buttons). Both are read through one
|
|
238
|
+
runtime property, so an app retunes them without recompiling:
|
|
239
|
+
|
|
240
|
+
```css
|
|
241
|
+
.smwb-appbar.smwb-appbar_variant-window {
|
|
242
|
+
--smui-appBarWindowControlsWidth: 84px;
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`--smui-appBarWindowControlsWidth` is deliberately **not** declared in `variables.less`: it is read with a fallback,
|
|
247
|
+
so unset means the Less defaults above. With `windowControls="auto"` the insets come from the shell instead —
|
|
248
|
+
`env(titlebar-area-x)` / `env(titlebar-area-width)` — and are 0 in a plain browser tab; `left`, `right` and `none`
|
|
249
|
+
pin them with a class. The insets are `padding-left` / `padding-right` on the bar, added to `@appBarPaddingX`, and
|
|
250
|
+
they survive `dense`. Overlays are `no-drag` globally — `[data-smwb-overlay-layer]`, `.smwb-menu`, `.smwb-tooltip`,
|
|
251
|
+
`.smwb-snackbars` and the library's own full-screen portals (`.smwb-chat-attachment-image__overlay`,
|
|
252
|
+
`.smwb-image-modal__overlay`, `.smwb-thumb-card__preview`, `.smwb-chat-shell`): Electron hit-tests drag regions by
|
|
253
|
+
geometry, and those popups portal to `<body>`. Inside the bar, the roles that keep `tabindex="-1"` while the group
|
|
254
|
+
holds the tab stop (`menuitemcheckbox`, `menuitemradio`, `treeitem`, `spinbutton`, `gridcell`, `listbox`,
|
|
255
|
+
`scrollbar`) are listed next to the focusable elements. A control the generic list cannot see opts out with
|
|
256
|
+
`.smwb-appbar__no-drag` or `data-no-drag`.
|
|
257
|
+
|
|
60
258
|
## License
|
|
61
259
|
|
|
62
260
|
[MIT](./LICENSE)
|
|
@@ -39,6 +39,90 @@
|
|
|
39
39
|
padding: @appBarActionPadding;
|
|
40
40
|
height: 48px;
|
|
41
41
|
}
|
|
42
|
+
// `variant="window"`: the bar is the title bar of a desktop app window (Electron, Tauri, an installed PWA
|
|
43
|
+
// with Window Controls Overlay). Compound and ordered after `&.smwb-dense` above, so its side insets
|
|
44
|
+
// out-rank the dense `padding` shorthand instead of being wiped by it.
|
|
45
|
+
&.smwb-appbar_variant-window {
|
|
46
|
+
-webkit-app-region: drag;
|
|
47
|
+
app-region: drag;
|
|
48
|
+
// Dragging the window out of the title must not select the title text.
|
|
49
|
+
user-select: none;
|
|
50
|
+
// Width to keep clear of the OS window buttons on each side. With no class at all (`windowControls="auto"`)
|
|
51
|
+
// the area comes from the shell: `env(titlebar-area-*)` is exact under Window Controls Overlay, and in a
|
|
52
|
+
// plain browser tab the fallbacks make both insets 0, so the bar looks like `variant="default"`.
|
|
53
|
+
--smwb-appbar-wc-left: env(titlebar-area-x, 0px);
|
|
54
|
+
--smwb-appbar-wc-right: calc(100vw - env(titlebar-area-x, 0px) - env(titlebar-area-width, 100vw));
|
|
55
|
+
padding-left: calc(@appBarPaddingX + var(--smwb-appbar-wc-left));
|
|
56
|
+
padding-right: calc(@appBarPaddingX + var(--smwb-appbar-wc-right));
|
|
57
|
+
// `left`: the macOS traffic lights (`titleBarStyle: "hiddenInset"`), which Electron does not report
|
|
58
|
+
// through env() unless `titleBarOverlay` is on, hence the fixed width.
|
|
59
|
+
&.smwb-appbar_window-controls-left {
|
|
60
|
+
--smwb-appbar-wc-left: env(titlebar-area-x, var(--smui-appBarWindowControlsWidth, @appBarWindowControlsLeft));
|
|
61
|
+
}
|
|
62
|
+
// `right`: the caption buttons Windows/Linux draw at the right edge of the window. The fallback keeps
|
|
63
|
+
// their width clear by shortening the area the shell would have reported.
|
|
64
|
+
&.smwb-appbar_window-controls-right {
|
|
65
|
+
--smwb-appbar-wc-right: calc(
|
|
66
|
+
100vw - env(titlebar-area-x, 0px) -
|
|
67
|
+
env(titlebar-area-width, calc(100vw - var(--smui-appBarWindowControlsWidth, @appBarWindowControlsRight)))
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
&.smwb-appbar_window-controls-none {
|
|
71
|
+
--smwb-appbar-wc-left: 0px;
|
|
72
|
+
--smwb-appbar-wc-right: 0px;
|
|
73
|
+
}
|
|
74
|
+
// Electron delivers no mouse events at all inside a drag region, so every control in the bar has to opt
|
|
75
|
+
// out. Focusable and role-tagged elements are matched generically (the list is `:where()`, so an app's own
|
|
76
|
+
// selector still wins); a custom control that is neither takes `.smwb-appbar__no-drag` or `data-no-drag`.
|
|
77
|
+
:where(
|
|
78
|
+
a[href],
|
|
79
|
+
button,
|
|
80
|
+
input,
|
|
81
|
+
select,
|
|
82
|
+
textarea,
|
|
83
|
+
label,
|
|
84
|
+
summary,
|
|
85
|
+
iframe,
|
|
86
|
+
video[controls],
|
|
87
|
+
[contenteditable]:not([contenteditable="false"]),
|
|
88
|
+
[tabindex]:not([tabindex="-1"]),
|
|
89
|
+
[role="button"],
|
|
90
|
+
[role="link"],
|
|
91
|
+
[role="menuitem"],
|
|
92
|
+
[role="tab"],
|
|
93
|
+
[role="checkbox"],
|
|
94
|
+
[role="switch"],
|
|
95
|
+
[role="radio"],
|
|
96
|
+
[role="combobox"],
|
|
97
|
+
[role="textbox"],
|
|
98
|
+
[role="searchbox"],
|
|
99
|
+
[role="slider"],
|
|
100
|
+
[role="option"],
|
|
101
|
+
// Roving-tabindex widgets and the cells of composite widgets: while the group holds
|
|
102
|
+
// the tab stop their items carry `tabindex="-1"`, so `[tabindex]:not([tabindex="-1"])`
|
|
103
|
+
// above never matches them and only the role gets them out of the drag region.
|
|
104
|
+
[role="menuitemcheckbox"],
|
|
105
|
+
[role="menuitemradio"],
|
|
106
|
+
[role="treeitem"],
|
|
107
|
+
[role="spinbutton"],
|
|
108
|
+
[role="gridcell"],
|
|
109
|
+
[role="listbox"],
|
|
110
|
+
[role="scrollbar"],
|
|
111
|
+
.smwb-tooltip__wrapper,
|
|
112
|
+
.smwb-appbar__no-drag,
|
|
113
|
+
[data-no-drag]
|
|
114
|
+
) {
|
|
115
|
+
-webkit-app-region: no-drag;
|
|
116
|
+
app-region: no-drag;
|
|
117
|
+
}
|
|
118
|
+
// Dense keeps the side insets and drops the vertical padding, which the 48px height has no room for.
|
|
119
|
+
// The insets are repeated here on purpose: `responsive.less` is imported after every component and resets
|
|
120
|
+
// `.smwb-appbar.smwb-dense` at phone widths, which would otherwise beat the two longhands above.
|
|
121
|
+
&.smwb-dense {
|
|
122
|
+
padding: 0 calc(@appBarPaddingX + var(--smwb-appbar-wc-right)) 0
|
|
123
|
+
calc(@appBarPaddingX + var(--smwb-appbar-wc-left));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
42
126
|
&__inner {
|
|
43
127
|
display: flex;
|
|
44
128
|
align-items: center;
|
|
@@ -52,6 +136,12 @@
|
|
|
52
136
|
gap: @appBarActionsGap;
|
|
53
137
|
white-space: nowrap;
|
|
54
138
|
}
|
|
139
|
+
// The title may be a heading (`titleAs`): drop the UA margins and font so it
|
|
140
|
+
// looks the same as the div.
|
|
141
|
+
&__left__title {
|
|
142
|
+
margin: 0;
|
|
143
|
+
font: inherit;
|
|
144
|
+
}
|
|
55
145
|
&__right {
|
|
56
146
|
white-space: nowrap;
|
|
57
147
|
}
|
|
@@ -74,6 +164,24 @@
|
|
|
74
164
|
}
|
|
75
165
|
}
|
|
76
166
|
|
|
167
|
+
// Overlays opened from a `variant="window"` bar are portaled to `<body>`, outside the bar's own subtree, but
|
|
168
|
+
// Electron hit-tests drag regions by geometry: a dropdown drawn over the bar would be unclickable. `no-drag`
|
|
169
|
+
// is ignored outside app windows, so this is harmless for every other page. The library's own full-screen
|
|
170
|
+
// portals are listed by class because they carry no `data-smwb-overlay-layer`: a lightbox opened over the top
|
|
171
|
+
// of the bar would otherwise stay a drag region, so a backdrop click there would drag the window instead of
|
|
172
|
+
// closing it.
|
|
173
|
+
[data-smwb-overlay-layer],
|
|
174
|
+
.smwb-menu,
|
|
175
|
+
.smwb-tooltip,
|
|
176
|
+
.smwb-snackbars,
|
|
177
|
+
.smwb-chat-attachment-image__overlay,
|
|
178
|
+
.smwb-image-modal__overlay,
|
|
179
|
+
.smwb-thumb-card__preview,
|
|
180
|
+
.smwb-chat-shell {
|
|
181
|
+
-webkit-app-region: no-drag;
|
|
182
|
+
app-region: no-drag;
|
|
183
|
+
}
|
|
184
|
+
|
|
77
185
|
.smwb-appbar_color-primary {
|
|
78
186
|
background-color: var(--smui-primaryColor);
|
|
79
187
|
color: var(--smui-onPrimaryColor);
|
|
@@ -58,6 +58,9 @@
|
|
|
58
58
|
display: inline-flex;
|
|
59
59
|
flex-direction: row;
|
|
60
60
|
align-items: center;
|
|
61
|
+
// The children's z-indexes stay inside the group: they must not lift an
|
|
62
|
+
// avatar over the rest of the page (a sticky header, a menu).
|
|
63
|
+
isolation: isolate;
|
|
61
64
|
|
|
62
65
|
// Direct children overlap, so an avatar wrapped in a link (a compact
|
|
63
66
|
// PersonChip) tucks in like a bare one.
|
|
@@ -65,6 +68,16 @@
|
|
|
65
68
|
margin-left: (-1 * @avatarGroupOverlap);
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
// Drawn in source order, each child would lie over the previous one: a
|
|
72
|
+
// z-index by position puts each over the next instead.
|
|
73
|
+
.smwb-avatar-group-stack(1);
|
|
74
|
+
|
|
75
|
+
// A focused child comes up whole, focus ring and all.
|
|
76
|
+
> :focus-visible,
|
|
77
|
+
> :has(:focus-visible) {
|
|
78
|
+
z-index: (@avatarGroupMaxStacked + 1);
|
|
79
|
+
}
|
|
80
|
+
|
|
68
81
|
.smwb-avatar {
|
|
69
82
|
border: @avatarGroupBorder solid var(--smui-surfaceBackground);
|
|
70
83
|
box-sizing: content-box;
|
|
@@ -80,3 +93,12 @@
|
|
|
80
93
|
font-size: 0.8rem;
|
|
81
94
|
}
|
|
82
95
|
}
|
|
96
|
+
|
|
97
|
+
// The first child highest. Past @avatarGroupMaxStacked children the rest keep
|
|
98
|
+
// no z-index, so they overlap the other way.
|
|
99
|
+
.smwb-avatar-group-stack(@n) when (@n <= @avatarGroupMaxStacked) {
|
|
100
|
+
> :nth-child(@{n}) {
|
|
101
|
+
z-index: (@avatarGroupMaxStacked + 1 - @n);
|
|
102
|
+
}
|
|
103
|
+
.smwb-avatar-group-stack(@n + 1);
|
|
104
|
+
}
|
|
@@ -33,14 +33,17 @@
|
|
|
33
33
|
&.smwb-small {
|
|
34
34
|
font-size: @smallButtonFontSize;
|
|
35
35
|
padding: @buttonPaddingSmall;
|
|
36
|
+
min-height: var(--smui-control-h-sm, @smui-control-h-sm);
|
|
36
37
|
}
|
|
37
38
|
&.smwb-medium {
|
|
38
39
|
font-size: @mediumButtonFontSize;
|
|
39
40
|
padding: @buttonPaddingMedium;
|
|
41
|
+
min-height: var(--smui-control-h-md, @smui-control-h-md);
|
|
40
42
|
}
|
|
41
43
|
&.smwb-large {
|
|
42
44
|
font-size: @largeButtonFontSize;
|
|
43
45
|
padding: @buttonPaddingLarge;
|
|
46
|
+
min-height: var(--smui-control-h-lg, @smui-control-h-lg);
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
&.smwb-contained {
|
|
@@ -52,7 +55,10 @@
|
|
|
52
55
|
}
|
|
53
56
|
&:hover {
|
|
54
57
|
background-color: var(--smui-buttonContainedHoverBg);
|
|
55
|
-
box-shadow:
|
|
58
|
+
box-shadow:
|
|
59
|
+
0 4px 22px -2px rgba(0, 0, 0, 0.2),
|
|
60
|
+
0 2px 2px 0 rgba(0, 0, 0, 0.14),
|
|
61
|
+
0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
|
56
62
|
}
|
|
57
63
|
&:disabled {
|
|
58
64
|
background-color: var(--smui-buttonDisabledBg);
|
|
@@ -39,6 +39,16 @@
|
|
|
39
39
|
pointer-events: none;
|
|
40
40
|
opacity: 0.38;
|
|
41
41
|
}
|
|
42
|
+
// The title and the children: the card's button when it has onClick. The
|
|
43
|
+
// footer follows it, beside the button, not inside it.
|
|
44
|
+
&__content {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
// The card draws the ring, below.
|
|
48
|
+
&:focus-visible {
|
|
49
|
+
outline: none;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
42
52
|
&__title {
|
|
43
53
|
font-size: 1.25rem;
|
|
44
54
|
font-weight: 500;
|
|
@@ -48,9 +58,10 @@
|
|
|
48
58
|
}
|
|
49
59
|
&_interactive {
|
|
50
60
|
cursor: pointer;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
}
|
|
62
|
+
// Focus goes to the card's button; the ring goes around the whole card.
|
|
63
|
+
&:has(> &__content:focus-visible) {
|
|
64
|
+
outline: 2px solid var(--smui-primaryColor);
|
|
65
|
+
outline-offset: 2px;
|
|
55
66
|
}
|
|
56
67
|
}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
// Emits --smwb-chart-series-N from @chartSeriesN<suffix> (suffix "" or "Dark")
|
|
2
|
+
// for N = 1 … 12. A slot that is `false` (the default of 8-12) emits nothing,
|
|
3
|
+
// so an unset slot leaves no token and the chart draws only the colours set.
|
|
4
|
+
.smwb-chart-series(@suffix; @i: 1) when (@i <= 12) {
|
|
5
|
+
@name: ~"chartSeries@{i}@{suffix}";
|
|
6
|
+
.smwb-chart-series-token(@i; @@name);
|
|
7
|
+
.smwb-chart-series(@suffix; @i + 1);
|
|
8
|
+
}
|
|
9
|
+
.smwb-chart-series-token(@i; @colour) when not (@colour = false) {
|
|
10
|
+
--smwb-chart-series-@{i}: @colour;
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
// A chart: ECharts draws into __canvas; until it does, a skeleton of the same
|
|
2
14
|
// size stands in, so the page does not jump.
|
|
3
15
|
.smwb-chart {
|
|
4
16
|
// Series colours, in order (read up to 12). The set stays apart with
|
|
5
|
-
// colour-vision deficiencies (Okabe–Ito); a theme
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
--smwb-chart-series-3: #009e73;
|
|
9
|
-
--smwb-chart-series-4: #cc79a7;
|
|
10
|
-
--smwb-chart-series-5: #56b4e9;
|
|
11
|
-
--smwb-chart-series-6: #d55e00;
|
|
12
|
-
--smwb-chart-series-7: #7a7a7a;
|
|
17
|
+
// colour-vision deficiencies (Okabe–Ito); a theme sets its own with
|
|
18
|
+
// @chartSeries1 … @chartSeries12 (and …Dark for the dark theme).
|
|
19
|
+
.smwb-chart-series("");
|
|
13
20
|
|
|
14
21
|
position: relative;
|
|
15
22
|
width: 100%;
|
|
@@ -41,11 +48,5 @@
|
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
:root[data-theme="dark"] .smwb-chart {
|
|
44
|
-
|
|
45
|
-
--smwb-chart-series-2: #f0b43a;
|
|
46
|
-
--smwb-chart-series-3: #2fc59b;
|
|
47
|
-
--smwb-chart-series-4: #e08cc0;
|
|
48
|
-
--smwb-chart-series-5: #8fd0f5;
|
|
49
|
-
--smwb-chart-series-6: #f27c3c;
|
|
50
|
-
--smwb-chart-series-7: #a8b0bc;
|
|
51
|
+
.smwb-chart-series("Dark");
|
|
51
52
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
.smwb-text-field-checkbox {
|
|
2
|
-
|
|
2
|
+
min-width: 0 !important;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
.smwb-checkbox {
|
|
6
|
+
// The box's size, which the native input takes too. The medium default is the
|
|
7
|
+
// selection token (18px), so the component's unstyled rendering is unchanged.
|
|
8
|
+
@box-size: @smui-selection-md;
|
|
9
|
+
|
|
6
10
|
&__icon {
|
|
7
11
|
opacity: 0;
|
|
8
12
|
transition: all 0.1s ease-in-out;
|
|
@@ -11,8 +15,8 @@
|
|
|
11
15
|
&__btn {
|
|
12
16
|
user-select: none;
|
|
13
17
|
flex-shrink: 0;
|
|
14
|
-
width:
|
|
15
|
-
height:
|
|
18
|
+
width: @box-size;
|
|
19
|
+
height: @box-size;
|
|
16
20
|
border-radius: @checkboxBorderRadius;
|
|
17
21
|
border: 2px solid var(--smui-checkboxBorderColor);
|
|
18
22
|
background-color: transparent;
|
|
@@ -23,6 +27,10 @@
|
|
|
23
27
|
position: relative;
|
|
24
28
|
transition: all 0.2s ease-in-out;
|
|
25
29
|
&::before {
|
|
30
|
+
// The hover/active halo stays 40px at every size. It is already the box's
|
|
31
|
+
// own minimum touch target (40px >= the md row height) and scales from the
|
|
32
|
+
// box's center, so shrinking it for sm would buy nothing and drop the
|
|
33
|
+
// largest size's halo below the 22px box it is meant to surround.
|
|
26
34
|
width: 40px;
|
|
27
35
|
height: 40px;
|
|
28
36
|
border-radius: @circleBorderRadius;
|
|
@@ -36,6 +44,8 @@
|
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
&__wrapper {
|
|
47
|
+
// Anchors the absolutely positioned input (below) to the control itself.
|
|
48
|
+
position: relative;
|
|
39
49
|
color: var(--smui-primaryTextColor);
|
|
40
50
|
display: flex;
|
|
41
51
|
align-items: center;
|
|
@@ -58,7 +68,19 @@
|
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
&__input {
|
|
61
|
-
display: none
|
|
71
|
+
// Visually hidden but rendered: with display: none the input would be out of
|
|
72
|
+
// the tab order and the accessibility tree. It lies under the box, which
|
|
73
|
+
// takes the pointer and passes clicks on through the label. With no insets
|
|
74
|
+
// of its own it sits where the box does: at the start, centered.
|
|
75
|
+
position: absolute;
|
|
76
|
+
width: @box-size;
|
|
77
|
+
height: @box-size;
|
|
78
|
+
margin: 0;
|
|
79
|
+
opacity: 0;
|
|
80
|
+
&:focus-visible ~ .smwb-checkbox__btn {
|
|
81
|
+
outline: 2px solid var(--smui-primaryColor);
|
|
82
|
+
outline-offset: 2px;
|
|
83
|
+
}
|
|
62
84
|
&:hover {
|
|
63
85
|
& ~ .smwb-checkbox__btn::before {
|
|
64
86
|
background-color: var(--smui-onSurfaceHover);
|
|
@@ -106,4 +128,33 @@
|
|
|
106
128
|
}
|
|
107
129
|
}
|
|
108
130
|
}
|
|
131
|
+
|
|
132
|
+
// The size prop maps the row onto the control-height scale and the box onto the
|
|
133
|
+
// selection scale. The size class rides the wrapper (the row), so min-height
|
|
134
|
+
// goes there; the box keeps a hard height, since a checkbox box must not stretch
|
|
135
|
+
// to fill the row. The hidden native input is resized with the box so its hit
|
|
136
|
+
// area stays coincident. medium is the component default and repeats today's
|
|
137
|
+
// 18px box exactly, so unstyled rendering does not change.
|
|
138
|
+
&_size-small {
|
|
139
|
+
min-height: var(--smui-control-h-sm, @smui-control-h-sm);
|
|
140
|
+
.smwb-checkbox__btn,
|
|
141
|
+
.smwb-checkbox__input {
|
|
142
|
+
width: @smui-selection-sm;
|
|
143
|
+
height: @smui-selection-sm;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// The medium box stays on the base rule's @box-size: @smui-selection-md
|
|
147
|
+
// read — repeating it here would only raise specificity and break consumer
|
|
148
|
+
// overrides of the default checkbox.
|
|
149
|
+
&_size-medium {
|
|
150
|
+
min-height: var(--smui-control-h-md, @smui-control-h-md);
|
|
151
|
+
}
|
|
152
|
+
&_size-large {
|
|
153
|
+
min-height: var(--smui-control-h-lg, @smui-control-h-lg);
|
|
154
|
+
.smwb-checkbox__btn,
|
|
155
|
+
.smwb-checkbox__input {
|
|
156
|
+
width: @smui-selection-lg;
|
|
157
|
+
height: @smui-selection-lg;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
109
160
|
}
|