al-design-system 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/README.pt-BR.md +6 -5
- package/dist/al.css +366 -2
- package/dist/components/number-field.css +363 -0
- package/dist/components/number-field.js +83 -0
- package/dist/foundation.css +1 -1
- package/dist/tokens.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ An open-source design system, from Figma to code. Plain CSS, a little vanilla Ja
|
|
|
10
10
|
|
|
11
11
|
## What it is
|
|
12
12
|
|
|
13
|
-
- **Foundation +
|
|
13
|
+
- **Foundation + 27 components**, designed in Figma and coded from the same tokens.
|
|
14
14
|
- **Light and dark themes** built in. It follows the operating system by default, and you can force one.
|
|
15
15
|
- **Accessibility is checked by the build.** Every rendered combination (variant × state × theme) is measured for contrast against the background it actually sits on, and each component's markup contract is verified. If something fails, the build stops.
|
|
16
16
|
- **Nothing is typed by hand.** Tokens, CSS variables, `tokens.json` and the documentation tables are all generated from one source.
|
|
@@ -32,6 +32,7 @@ An open-source design system, from Figma to code. Plain CSS, a little vanilla Ja
|
|
|
32
32
|
| | Input | | [guidelines](src/components/input/guidelines.md) |
|
|
33
33
|
| | Textarea | | [guidelines](src/components/textarea/guidelines.md) |
|
|
34
34
|
| | Password | ✓ | [guidelines](src/components/password/guidelines.md) |
|
|
35
|
+
| | Number field | ✓ | [guidelines](src/components/number-field/guidelines.md) |
|
|
35
36
|
| Structure and overlays | Divider | | [guidelines](src/components/divider/guidelines.md) |
|
|
36
37
|
| | Card | | [guidelines](src/components/card/guidelines.md) |
|
|
37
38
|
| | Tab | ✓ | [guidelines](src/components/tab/guidelines.md) |
|
|
@@ -57,7 +58,7 @@ npm install al-design-system
|
|
|
57
58
|
Or straight from a CDN, pinned to a version:
|
|
58
59
|
|
|
59
60
|
```html
|
|
60
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.
|
|
61
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.4.0/dist/al.css">
|
|
61
62
|
```
|
|
62
63
|
|
|
63
64
|
Each [GitHub Release](https://github.com/guilhermedworakowski/al-design-system/releases) also carries the `dist/` folder as a zip.
|
|
@@ -66,10 +67,10 @@ Each [GitHub Release](https://github.com/guilhermedworakowski/al-design-system/r
|
|
|
66
67
|
|
|
67
68
|
| File | What it is |
|
|
68
69
|
|---|---|
|
|
69
|
-
| `al.css` | Foundation + all
|
|
70
|
+
| `al.css` | Foundation + all 27 components, in one file |
|
|
70
71
|
| `foundation.css` | Only the Foundation: color, typography, spacing, radius, icon size, elevation, focus ring and motion |
|
|
71
72
|
| `components/<name>.css` | One component (its tokens + its CSS) |
|
|
72
|
-
| `components/<name>.js` | The behavior, for the
|
|
73
|
+
| `components/<name>.js` | The behavior, for the 10 components that have a script |
|
|
73
74
|
| `icons/*.svg`, `icons/icons.json` | The 70 icons |
|
|
74
75
|
| `tokens.json` | The Foundation tokens, to read from code |
|
|
75
76
|
|
|
@@ -100,7 +101,7 @@ The exact markup of each component (which element, which attributes) is describe
|
|
|
100
101
|
|
|
101
102
|
### Scripts
|
|
102
103
|
|
|
103
|
-
Password, Tab, Modal, Drawer, Sidebar, Breadcrumb, Tooltip, Toast and Alert need their script, loaded once per page. Each one wires up the components already on the page by itself:
|
|
104
|
+
Password, Number field, Tab, Modal, Drawer, Sidebar, Breadcrumb, Tooltip, Toast and Alert need their script, loaded once per page. Each one wires up the components already on the page by itself:
|
|
104
105
|
|
|
105
106
|
```html
|
|
106
107
|
<script src="node_modules/al-design-system/dist/components/modal.js" defer></script>
|
package/README.pt-BR.md
CHANGED
|
@@ -10,7 +10,7 @@ Design system open source, do Figma ao código. CSS puro, um pouco de JavaScript
|
|
|
10
10
|
|
|
11
11
|
## O que é
|
|
12
12
|
|
|
13
|
-
- **Foundation +
|
|
13
|
+
- **Foundation + 27 componentes**, desenhados no Figma e codados a partir dos mesmos tokens.
|
|
14
14
|
- **Temas claro e escuro** já incluídos. Por padrão segue o sistema operacional, e dá para forçar um.
|
|
15
15
|
- **A acessibilidade é conferida no build.** Cada combinação renderizada (variante × estado × tema) tem o contraste medido contra o fundo onde ela realmente aparece, e o contrato de marcação de cada componente é verificado. Se algo reprova, o build para.
|
|
16
16
|
- **Nada é digitado à mão.** Tokens, variáveis CSS, `tokens.json` e as tabelas da documentação saem todos de uma fonte só.
|
|
@@ -32,6 +32,7 @@ Design system open source, do Figma ao código. CSS puro, um pouco de JavaScript
|
|
|
32
32
|
| | Input | | [diretrizes](src/components/input/guidelines.md) |
|
|
33
33
|
| | Textarea | | [diretrizes](src/components/textarea/guidelines.md) |
|
|
34
34
|
| | Password | ✓ | [diretrizes](src/components/password/guidelines.md) |
|
|
35
|
+
| | Number field | ✓ | [diretrizes](src/components/number-field/guidelines.md) |
|
|
35
36
|
| Estrutura e sobreposição | Divider | | [diretrizes](src/components/divider/guidelines.md) |
|
|
36
37
|
| | Card | | [diretrizes](src/components/card/guidelines.md) |
|
|
37
38
|
| | Tab | ✓ | [diretrizes](src/components/tab/guidelines.md) |
|
|
@@ -57,7 +58,7 @@ npm install al-design-system
|
|
|
57
58
|
Ou direto de uma CDN, fixado numa versão:
|
|
58
59
|
|
|
59
60
|
```html
|
|
60
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.
|
|
61
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.4.0/dist/al.css">
|
|
61
62
|
```
|
|
62
63
|
|
|
63
64
|
Cada [Release no GitHub](https://github.com/guilhermedworakowski/al-design-system/releases) também traz a pasta `dist/` num zip.
|
|
@@ -66,10 +67,10 @@ Cada [Release no GitHub](https://github.com/guilhermedworakowski/al-design-syste
|
|
|
66
67
|
|
|
67
68
|
| Arquivo | O que é |
|
|
68
69
|
|---|---|
|
|
69
|
-
| `al.css` | Foundation + os
|
|
70
|
+
| `al.css` | Foundation + os 27 componentes, num arquivo só |
|
|
70
71
|
| `foundation.css` | Só a Foundation: cor, tipografia, espaçamento, radius, tamanho de ícone, elevação, anel de foco e motion |
|
|
71
72
|
| `components/<nome>.css` | Um componente (os tokens + o CSS dele) |
|
|
72
|
-
| `components/<nome>.js` | O comportamento, para os
|
|
73
|
+
| `components/<nome>.js` | O comportamento, para os 10 componentes que têm script |
|
|
73
74
|
| `icons/*.svg`, `icons/icons.json` | Os 70 ícones |
|
|
74
75
|
| `tokens.json` | Os tokens da Foundation, para ler por código |
|
|
75
76
|
|
|
@@ -100,7 +101,7 @@ A marcação exata de cada componente (qual elemento, quais atributos) está des
|
|
|
100
101
|
|
|
101
102
|
### Scripts
|
|
102
103
|
|
|
103
|
-
Password, Tab, Modal, Drawer, Sidebar, Breadcrumb, Tooltip, Toast e Alert precisam do script, carregado uma vez por página. Cada um liga sozinho os componentes que já estão na página:
|
|
104
|
+
Password, Number field, Tab, Modal, Drawer, Sidebar, Breadcrumb, Tooltip, Toast e Alert precisam do script, carregado uma vez por página. Cada um liga sozinho os componentes que já estão na página:
|
|
104
105
|
|
|
105
106
|
```html
|
|
106
107
|
<script src="node_modules/al-design-system/dist/components/modal.js" defer></script>
|
package/dist/al.css
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/* AL Design System 1.
|
|
1
|
+
/* AL Design System 1.4.0 - Foundation + 27 components.
|
|
2
2
|
* MIT. https://github.com/guilhermedworakowski/al-design-system
|
|
3
3
|
*/
|
|
4
4
|
/* AL Design System - Foundation
|
|
5
|
-
* v1.
|
|
5
|
+
* v1.4.0 - MIT
|
|
6
6
|
* GENERATED by src/foundation/css.py. Do not edit by hand: run the script.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -7243,3 +7243,367 @@
|
|
|
7243
7243
|
outline-offset: var(--al-border-focus-offset);
|
|
7244
7244
|
}
|
|
7245
7245
|
}
|
|
7246
|
+
|
|
7247
|
+
/* AL Design System - Number field tokens
|
|
7248
|
+
* GENERATED by src/components/number-field/tokens.py. Do not edit by hand.
|
|
7249
|
+
*
|
|
7250
|
+
* There is no theme block here: each token points to a semantic, and the
|
|
7251
|
+
* theme switches on :root - the same element where these aliases are declared.
|
|
7252
|
+
*
|
|
7253
|
+
* Field height: 48px, derived, no token.
|
|
7254
|
+
* Stepper target: 24px = icon-size, no token of its own.
|
|
7255
|
+
*/
|
|
7256
|
+
|
|
7257
|
+
:root {
|
|
7258
|
+
|
|
7259
|
+
/* background - only disabled and readonly change it */
|
|
7260
|
+
--al-number-field-bg: var(--al-bg-surface-raised);
|
|
7261
|
+
--al-number-field-bg-disabled: var(--al-bg-disabled);
|
|
7262
|
+
--al-number-field-bg-readonly: var(--al-bg-subtle);
|
|
7263
|
+
|
|
7264
|
+
/* border - the axis that carries the state */
|
|
7265
|
+
--al-number-field-border: var(--al-border-default);
|
|
7266
|
+
--al-number-field-border-hover: var(--al-border-strong);
|
|
7267
|
+
--al-number-field-border-focus: var(--al-border-brand);
|
|
7268
|
+
--al-number-field-border-error: var(--al-border-danger);
|
|
7269
|
+
--al-number-field-border-disabled: var(--al-border-default);
|
|
7270
|
+
--al-number-field-border-readonly: var(--al-border-subtle);
|
|
7271
|
+
|
|
7272
|
+
/* value - no placeholder, the field always has a value */
|
|
7273
|
+
--al-number-field-text-value: var(--al-text-primary);
|
|
7274
|
+
--al-number-field-text-disabled: var(--al-text-disabled);
|
|
7275
|
+
|
|
7276
|
+
/* steppers - out of the Tab order, no hover or ring of their own */
|
|
7277
|
+
--al-number-field-stepper-icon: var(--al-text-primary);
|
|
7278
|
+
--al-number-field-stepper-icon-disabled: var(--al-text-disabled);
|
|
7279
|
+
|
|
7280
|
+
/* label, mark and help - they live outside the field, on the canvas */
|
|
7281
|
+
--al-number-field-label: var(--al-text-primary);
|
|
7282
|
+
--al-number-field-label-error: var(--al-text-danger);
|
|
7283
|
+
--al-number-field-label-disabled: var(--al-text-disabled);
|
|
7284
|
+
--al-number-field-optional: var(--al-text-secondary);
|
|
7285
|
+
--al-number-field-help: var(--al-text-secondary);
|
|
7286
|
+
--al-number-field-help-error: var(--al-text-danger);
|
|
7287
|
+
|
|
7288
|
+
/* focus ring - points to the composite shadow, not the raw color */
|
|
7289
|
+
--al-number-field-ring: var(--al-focus-ring-default);
|
|
7290
|
+
--al-number-field-ring-error: var(--al-focus-ring-error);
|
|
7291
|
+
|
|
7292
|
+
/* geometry */
|
|
7293
|
+
--al-number-field-padding-x: var(--al-space-12);
|
|
7294
|
+
--al-number-field-padding-y: var(--al-space-12);
|
|
7295
|
+
--al-number-field-gap: var(--al-space-8);
|
|
7296
|
+
--al-number-field-stepper-gap: var(--al-space-24);
|
|
7297
|
+
--al-number-field-radius: var(--al-radius-lg);
|
|
7298
|
+
--al-number-field-border-width: var(--al-border-width-1);
|
|
7299
|
+
--al-number-field-icon-size: var(--al-icon-size-24);
|
|
7300
|
+
|
|
7301
|
+
/* width of the box - declared literal, the Foundation has no width scale */
|
|
7302
|
+
--al-number-field-width: 140px;
|
|
7303
|
+
|
|
7304
|
+
/* typography - one style becomes four vars */
|
|
7305
|
+
--al-number-field-font-size: var(--al-font-size-md);
|
|
7306
|
+
--al-number-field-line-height: var(--al-line-height-md);
|
|
7307
|
+
--al-number-field-font-weight: 400;
|
|
7308
|
+
--al-number-field-tracking: 0em;
|
|
7309
|
+
--al-number-field-label-font-size: var(--al-font-size-md);
|
|
7310
|
+
--al-number-field-label-line-height: var(--al-line-height-md);
|
|
7311
|
+
--al-number-field-label-font-weight: 400;
|
|
7312
|
+
--al-number-field-label-tracking: 0em;
|
|
7313
|
+
--al-number-field-optional-font-size: var(--al-font-size-sm);
|
|
7314
|
+
--al-number-field-optional-line-height: var(--al-line-height-sm);
|
|
7315
|
+
--al-number-field-optional-font-weight: 400;
|
|
7316
|
+
--al-number-field-optional-tracking: 0em;
|
|
7317
|
+
--al-number-field-help-font-size: var(--al-font-size-xs);
|
|
7318
|
+
--al-number-field-help-line-height: var(--al-line-height-xs);
|
|
7319
|
+
--al-number-field-help-font-weight: 400;
|
|
7320
|
+
--al-number-field-help-tracking: 0.01em;
|
|
7321
|
+
}
|
|
7322
|
+
|
|
7323
|
+
/* AL Design System - Number field
|
|
7324
|
+
*
|
|
7325
|
+
* Depends on: foundation/al-foundation.css
|
|
7326
|
+
* + components/number-field/al-number-field-tokens.css
|
|
7327
|
+
* + components/icon/icon.css
|
|
7328
|
+
* + components/number-field/number-field.js (the steppers)
|
|
7329
|
+
*
|
|
7330
|
+
* A QUANTITY THE PERSON ADJUSTS A LITTLE AT A TIME
|
|
7331
|
+
* A native <input type="number"> between a - and a + button. Numbers that
|
|
7332
|
+
* are not quantities (zip code, phone, card) are the Input with
|
|
7333
|
+
* inputmode="numeric" - rule 2.
|
|
7334
|
+
*
|
|
7335
|
+
* ANATOMY
|
|
7336
|
+
* <div class="al-number-field">
|
|
7337
|
+
* <div class="al-number-field__labelrow">
|
|
7338
|
+
* <label class="al-number-field__label" for="qty">Quantity</label>
|
|
7339
|
+
* <span class="al-number-field__optional">(Optional)</span>
|
|
7340
|
+
* </div>
|
|
7341
|
+
* <div class="al-number-field__control">
|
|
7342
|
+
* <button class="al-number-field__step" type="button" tabindex="-1"
|
|
7343
|
+
* data-step="down" aria-controls="qty"
|
|
7344
|
+
* aria-label="Decrease quantity" hidden>
|
|
7345
|
+
* <svg class="al-icon" aria-hidden="true" focusable="false">minus</svg>
|
|
7346
|
+
* </button>
|
|
7347
|
+
* <input class="al-number-field__field" id="qty" type="number"
|
|
7348
|
+
* min="1" max="10" step="1" value="1"
|
|
7349
|
+
* aria-describedby="qty-help">
|
|
7350
|
+
* <button class="al-number-field__step" type="button" tabindex="-1"
|
|
7351
|
+
* data-step="up" aria-controls="qty"
|
|
7352
|
+
* aria-label="Increase quantity" hidden>
|
|
7353
|
+
* <svg class="al-icon" aria-hidden="true" focusable="false">plus</svg>
|
|
7354
|
+
* </button>
|
|
7355
|
+
* </div>
|
|
7356
|
+
* <p class="al-number-field__help" id="qty-help">From 1 to 10</p>
|
|
7357
|
+
* </div>
|
|
7358
|
+
*
|
|
7359
|
+
* THE STEPPERS ARE OUT OF THE TAB ORDER
|
|
7360
|
+
* One tab stop: the arrow keys and typing change the value (Carbon, rule
|
|
7361
|
+
* 13). So the buttons carry tabindex="-1", have no ring and no hover of
|
|
7362
|
+
* their own (rule 14) - the field's border and ring say where pointer and
|
|
7363
|
+
* focus are. They are born `hidden`: without JavaScript the arrow keys
|
|
7364
|
+
* still work and a button that does nothing never shows (the same as the
|
|
7365
|
+
* Password's eye).
|
|
7366
|
+
*
|
|
7367
|
+
* THE STEPPER AT THE LIMIT IS A DISABLED <button>
|
|
7368
|
+
* At the minimum the - is `disabled`, at the maximum the + (rule 10). The
|
|
7369
|
+
* JS sets the attribute; the CSS only reads `:disabled`. There is no Figma
|
|
7370
|
+
* variant for it.
|
|
7371
|
+
*
|
|
7372
|
+
* THE BOX IS `__control`, NOT THE <input>
|
|
7373
|
+
* The same as the Input: border, background, radius and ring live on
|
|
7374
|
+
* `__control` and the state is read through `:has()`. Without `:has()` the
|
|
7375
|
+
* native outline stays (it is only removed inside @supports).
|
|
7376
|
+
*
|
|
7377
|
+
* THE STATE IS DRIVEN BY THE MARKUP
|
|
7378
|
+
* Error reads `aria-invalid="true"`; disabled `:disabled`; read-only
|
|
7379
|
+
* `[readonly]` - never `:read-only`, which also matches disabled. The same
|
|
7380
|
+
* precedence by variable as the Input: error, read-only, disabled in file
|
|
7381
|
+
* order, disabled wins over all.
|
|
7382
|
+
*
|
|
7383
|
+
* READ-ONLY HAS NO STEPPERS (rule 17)
|
|
7384
|
+
* Nothing can change, so they are hidden and the value stays centered.
|
|
7385
|
+
*
|
|
7386
|
+
* THE BROWSER'S OWN SPINNERS ARE HIDDEN
|
|
7387
|
+
* WebKit draws arrows inside type=number and Firefox draws its own. With
|
|
7388
|
+
* AL's buttons there would be two pairs.
|
|
7389
|
+
*
|
|
7390
|
+
* HEIGHT AND WIDTH
|
|
7391
|
+
* 48 = padding-y x2 + line height, border subtracted from the padding.
|
|
7392
|
+
* The box is fixed at `--al-number-field-width` (140), decided on
|
|
7393
|
+
* 2026-10-21. Label and help text are not limited by it - they may be
|
|
7394
|
+
* wider than the box.
|
|
7395
|
+
*
|
|
7396
|
+
* HANDWRITTEN. The tokens it consumes are generated by tokens.py.
|
|
7397
|
+
*/
|
|
7398
|
+
|
|
7399
|
+
/* ---------------------------------------------------------------- root */
|
|
7400
|
+
.al-number-field {
|
|
7401
|
+
display: flex;
|
|
7402
|
+
flex-direction: column;
|
|
7403
|
+
gap: var(--al-number-field-gap);
|
|
7404
|
+
}
|
|
7405
|
+
|
|
7406
|
+
/* ------------------------------------------------------------ label */
|
|
7407
|
+
.al-number-field__labelrow {
|
|
7408
|
+
display: flex;
|
|
7409
|
+
align-items: baseline;
|
|
7410
|
+
gap: var(--al-number-field-gap);
|
|
7411
|
+
}
|
|
7412
|
+
|
|
7413
|
+
.al-number-field__label {
|
|
7414
|
+
color: var(--al-number-field-label);
|
|
7415
|
+
font-family: var(--al-font-sans);
|
|
7416
|
+
font-size: var(--al-number-field-label-font-size);
|
|
7417
|
+
line-height: var(--al-number-field-label-line-height);
|
|
7418
|
+
font-weight: var(--al-number-field-label-font-weight);
|
|
7419
|
+
letter-spacing: var(--al-number-field-label-tracking);
|
|
7420
|
+
}
|
|
7421
|
+
|
|
7422
|
+
.al-number-field__optional {
|
|
7423
|
+
color: var(--al-number-field-optional);
|
|
7424
|
+
font-family: var(--al-font-sans);
|
|
7425
|
+
font-size: var(--al-number-field-optional-font-size);
|
|
7426
|
+
line-height: var(--al-number-field-optional-line-height);
|
|
7427
|
+
font-weight: var(--al-number-field-optional-font-weight);
|
|
7428
|
+
letter-spacing: var(--al-number-field-optional-tracking);
|
|
7429
|
+
}
|
|
7430
|
+
|
|
7431
|
+
/* --------------------------------------------------------------- box */
|
|
7432
|
+
.al-number-field__control {
|
|
7433
|
+
--_border-state: var(--al-number-field-border);
|
|
7434
|
+
|
|
7435
|
+
box-sizing: border-box;
|
|
7436
|
+
display: flex;
|
|
7437
|
+
align-items: center;
|
|
7438
|
+
gap: var(--al-number-field-stepper-gap);
|
|
7439
|
+
width: var(--al-number-field-width);
|
|
7440
|
+
|
|
7441
|
+
padding: calc(var(--al-number-field-padding-y) - var(--al-number-field-border-width))
|
|
7442
|
+
calc(var(--al-number-field-padding-x) - var(--al-number-field-border-width));
|
|
7443
|
+
|
|
7444
|
+
border: var(--al-number-field-border-width) solid
|
|
7445
|
+
var(--_border-force, var(--_border-state));
|
|
7446
|
+
border-radius: var(--al-number-field-radius);
|
|
7447
|
+
background-color: var(--_bg-force, var(--al-number-field-bg));
|
|
7448
|
+
cursor: text;
|
|
7449
|
+
|
|
7450
|
+
transition:
|
|
7451
|
+
border-color var(--al-motion-duration-feedback) var(--al-motion-easing-exit),
|
|
7452
|
+
box-shadow var(--al-motion-duration-feedback) var(--al-motion-easing-exit);
|
|
7453
|
+
}
|
|
7454
|
+
|
|
7455
|
+
/* ------------------------------------------------------------- field */
|
|
7456
|
+
.al-number-field__field {
|
|
7457
|
+
--_text-state: var(--al-number-field-text-value);
|
|
7458
|
+
|
|
7459
|
+
flex: 1 1 0;
|
|
7460
|
+
min-width: 0;
|
|
7461
|
+
width: 100%;
|
|
7462
|
+
margin: 0;
|
|
7463
|
+
padding: 0;
|
|
7464
|
+
border: 0;
|
|
7465
|
+
background: none;
|
|
7466
|
+
color: var(--_text-force, var(--_text-state));
|
|
7467
|
+
text-align: center;
|
|
7468
|
+
|
|
7469
|
+
font-family: var(--al-font-sans);
|
|
7470
|
+
font-size: var(--al-number-field-font-size);
|
|
7471
|
+
line-height: var(--al-number-field-line-height);
|
|
7472
|
+
font-weight: var(--al-number-field-font-weight);
|
|
7473
|
+
letter-spacing: var(--al-number-field-tracking);
|
|
7474
|
+
font-variant-numeric: tabular-nums;
|
|
7475
|
+
|
|
7476
|
+
-moz-appearance: textfield;
|
|
7477
|
+
appearance: textfield;
|
|
7478
|
+
}
|
|
7479
|
+
|
|
7480
|
+
/* the browser's own spinners - see the header */
|
|
7481
|
+
.al-number-field__field::-webkit-inner-spin-button,
|
|
7482
|
+
.al-number-field__field::-webkit-outer-spin-button {
|
|
7483
|
+
-webkit-appearance: none;
|
|
7484
|
+
margin: 0;
|
|
7485
|
+
}
|
|
7486
|
+
|
|
7487
|
+
@supports selector(:has(*)) {
|
|
7488
|
+
.al-number-field__field:focus {
|
|
7489
|
+
outline: none;
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
|
|
7493
|
+
/* --------------------------------------------------------- the steppers */
|
|
7494
|
+
.al-number-field__step {
|
|
7495
|
+
display: inline-flex;
|
|
7496
|
+
align-items: center;
|
|
7497
|
+
justify-content: center;
|
|
7498
|
+
flex: none;
|
|
7499
|
+
|
|
7500
|
+
box-sizing: border-box;
|
|
7501
|
+
width: var(--al-number-field-icon-size);
|
|
7502
|
+
height: var(--al-number-field-icon-size);
|
|
7503
|
+
margin: 0;
|
|
7504
|
+
padding: 0;
|
|
7505
|
+
border: 0;
|
|
7506
|
+
background: none;
|
|
7507
|
+
/* the svg inherits from here through currentColor - the Icon contract */
|
|
7508
|
+
color: var(--al-number-field-stepper-icon);
|
|
7509
|
+
|
|
7510
|
+
cursor: pointer;
|
|
7511
|
+
-webkit-appearance: none;
|
|
7512
|
+
appearance: none;
|
|
7513
|
+
-webkit-tap-highlight-color: transparent;
|
|
7514
|
+
}
|
|
7515
|
+
|
|
7516
|
+
/* `display` above would beat the attribute - without JS the buttons don't show */
|
|
7517
|
+
.al-number-field__step[hidden] {
|
|
7518
|
+
display: none;
|
|
7519
|
+
}
|
|
7520
|
+
|
|
7521
|
+
.al-number-field__step > svg {
|
|
7522
|
+
display: block;
|
|
7523
|
+
flex: none;
|
|
7524
|
+
width: var(--al-number-field-icon-size);
|
|
7525
|
+
height: var(--al-number-field-icon-size);
|
|
7526
|
+
}
|
|
7527
|
+
|
|
7528
|
+
/* at the limit, and in a disabled field - rule 10 */
|
|
7529
|
+
.al-number-field__step:disabled {
|
|
7530
|
+
color: var(--al-number-field-stepper-icon-disabled);
|
|
7531
|
+
cursor: not-allowed;
|
|
7532
|
+
}
|
|
7533
|
+
|
|
7534
|
+
/* ------------------------------------------------------------ help text */
|
|
7535
|
+
.al-number-field__help {
|
|
7536
|
+
color: var(--al-number-field-help);
|
|
7537
|
+
font-family: var(--al-font-sans);
|
|
7538
|
+
font-size: var(--al-number-field-help-font-size);
|
|
7539
|
+
line-height: var(--al-number-field-help-line-height);
|
|
7540
|
+
font-weight: var(--al-number-field-help-font-weight);
|
|
7541
|
+
letter-spacing: var(--al-number-field-help-tracking);
|
|
7542
|
+
margin: 0;
|
|
7543
|
+
}
|
|
7544
|
+
|
|
7545
|
+
/* -------------------------------------------------------------- states */
|
|
7546
|
+
.al-number-field__control:hover:not(:has(.al-number-field__field:disabled, .al-number-field__field[readonly])) {
|
|
7547
|
+
--_border-state: var(--al-number-field-border-hover);
|
|
7548
|
+
}
|
|
7549
|
+
|
|
7550
|
+
.al-number-field__control:has(.al-number-field__field:focus-visible) {
|
|
7551
|
+
--_border-state: var(--al-number-field-border-focus);
|
|
7552
|
+
box-shadow: var(--_ring-force, var(--al-number-field-ring));
|
|
7553
|
+
}
|
|
7554
|
+
|
|
7555
|
+
/* error - beats hover and focus by writing to another property */
|
|
7556
|
+
.al-number-field__control:has(.al-number-field__field[aria-invalid="true"]) {
|
|
7557
|
+
--_border-force: var(--al-number-field-border-error);
|
|
7558
|
+
--_ring-force: var(--al-number-field-ring-error);
|
|
7559
|
+
}
|
|
7560
|
+
|
|
7561
|
+
.al-number-field:has(.al-number-field__field[aria-invalid="true"]) .al-number-field__label {
|
|
7562
|
+
color: var(--al-number-field-label-error);
|
|
7563
|
+
}
|
|
7564
|
+
|
|
7565
|
+
.al-number-field:has(.al-number-field__field[aria-invalid="true"]) .al-number-field__help {
|
|
7566
|
+
color: var(--al-number-field-help-error);
|
|
7567
|
+
}
|
|
7568
|
+
|
|
7569
|
+
/* read-only - `[readonly]`, never `:read-only`. No steppers - rule 17. */
|
|
7570
|
+
.al-number-field__control:has(.al-number-field__field[readonly]) {
|
|
7571
|
+
--_border-force: var(--al-number-field-border-readonly);
|
|
7572
|
+
--_bg-force: var(--al-number-field-bg-readonly);
|
|
7573
|
+
cursor: default;
|
|
7574
|
+
}
|
|
7575
|
+
|
|
7576
|
+
.al-number-field__control:has(.al-number-field__field[readonly]) .al-number-field__step {
|
|
7577
|
+
display: none;
|
|
7578
|
+
}
|
|
7579
|
+
|
|
7580
|
+
/* disabled - last, because it wins over all */
|
|
7581
|
+
.al-number-field__control:has(.al-number-field__field:disabled) {
|
|
7582
|
+
--_border-force: var(--al-number-field-border-disabled);
|
|
7583
|
+
--_bg-force: var(--al-number-field-bg-disabled);
|
|
7584
|
+
cursor: not-allowed;
|
|
7585
|
+
}
|
|
7586
|
+
|
|
7587
|
+
.al-number-field__field:disabled {
|
|
7588
|
+
--_text-force: var(--al-number-field-text-disabled);
|
|
7589
|
+
cursor: not-allowed;
|
|
7590
|
+
}
|
|
7591
|
+
|
|
7592
|
+
.al-number-field:has(.al-number-field__field:disabled) .al-number-field__label,
|
|
7593
|
+
.al-number-field:has(.al-number-field__field:disabled) .al-number-field__optional {
|
|
7594
|
+
color: var(--al-number-field-label-disabled);
|
|
7595
|
+
}
|
|
7596
|
+
|
|
7597
|
+
/* ENTER - CSS uses the curve of the state the transition goes to. */
|
|
7598
|
+
.al-number-field__control:hover,
|
|
7599
|
+
.al-number-field__control:has(.al-number-field__field:focus-visible),
|
|
7600
|
+
.al-number-field__control:has(.al-number-field__field[aria-invalid="true"]) {
|
|
7601
|
+
transition-timing-function: var(--al-motion-easing-enter);
|
|
7602
|
+
}
|
|
7603
|
+
|
|
7604
|
+
/* --------------------------------------------------------- environment */
|
|
7605
|
+
@media (prefers-reduced-motion: reduce) {
|
|
7606
|
+
.al-number-field__control {
|
|
7607
|
+
transition: none;
|
|
7608
|
+
}
|
|
7609
|
+
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/* AL Design System - Number field tokens
|
|
2
|
+
* GENERATED by src/components/number-field/tokens.py. Do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* There is no theme block here: each token points to a semantic, and the
|
|
5
|
+
* theme switches on :root - the same element where these aliases are declared.
|
|
6
|
+
*
|
|
7
|
+
* Field height: 48px, derived, no token.
|
|
8
|
+
* Stepper target: 24px = icon-size, no token of its own.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
:root {
|
|
12
|
+
|
|
13
|
+
/* background - only disabled and readonly change it */
|
|
14
|
+
--al-number-field-bg: var(--al-bg-surface-raised);
|
|
15
|
+
--al-number-field-bg-disabled: var(--al-bg-disabled);
|
|
16
|
+
--al-number-field-bg-readonly: var(--al-bg-subtle);
|
|
17
|
+
|
|
18
|
+
/* border - the axis that carries the state */
|
|
19
|
+
--al-number-field-border: var(--al-border-default);
|
|
20
|
+
--al-number-field-border-hover: var(--al-border-strong);
|
|
21
|
+
--al-number-field-border-focus: var(--al-border-brand);
|
|
22
|
+
--al-number-field-border-error: var(--al-border-danger);
|
|
23
|
+
--al-number-field-border-disabled: var(--al-border-default);
|
|
24
|
+
--al-number-field-border-readonly: var(--al-border-subtle);
|
|
25
|
+
|
|
26
|
+
/* value - no placeholder, the field always has a value */
|
|
27
|
+
--al-number-field-text-value: var(--al-text-primary);
|
|
28
|
+
--al-number-field-text-disabled: var(--al-text-disabled);
|
|
29
|
+
|
|
30
|
+
/* steppers - out of the Tab order, no hover or ring of their own */
|
|
31
|
+
--al-number-field-stepper-icon: var(--al-text-primary);
|
|
32
|
+
--al-number-field-stepper-icon-disabled: var(--al-text-disabled);
|
|
33
|
+
|
|
34
|
+
/* label, mark and help - they live outside the field, on the canvas */
|
|
35
|
+
--al-number-field-label: var(--al-text-primary);
|
|
36
|
+
--al-number-field-label-error: var(--al-text-danger);
|
|
37
|
+
--al-number-field-label-disabled: var(--al-text-disabled);
|
|
38
|
+
--al-number-field-optional: var(--al-text-secondary);
|
|
39
|
+
--al-number-field-help: var(--al-text-secondary);
|
|
40
|
+
--al-number-field-help-error: var(--al-text-danger);
|
|
41
|
+
|
|
42
|
+
/* focus ring - points to the composite shadow, not the raw color */
|
|
43
|
+
--al-number-field-ring: var(--al-focus-ring-default);
|
|
44
|
+
--al-number-field-ring-error: var(--al-focus-ring-error);
|
|
45
|
+
|
|
46
|
+
/* geometry */
|
|
47
|
+
--al-number-field-padding-x: var(--al-space-12);
|
|
48
|
+
--al-number-field-padding-y: var(--al-space-12);
|
|
49
|
+
--al-number-field-gap: var(--al-space-8);
|
|
50
|
+
--al-number-field-stepper-gap: var(--al-space-24);
|
|
51
|
+
--al-number-field-radius: var(--al-radius-lg);
|
|
52
|
+
--al-number-field-border-width: var(--al-border-width-1);
|
|
53
|
+
--al-number-field-icon-size: var(--al-icon-size-24);
|
|
54
|
+
|
|
55
|
+
/* width of the box - declared literal, the Foundation has no width scale */
|
|
56
|
+
--al-number-field-width: 140px;
|
|
57
|
+
|
|
58
|
+
/* typography - one style becomes four vars */
|
|
59
|
+
--al-number-field-font-size: var(--al-font-size-md);
|
|
60
|
+
--al-number-field-line-height: var(--al-line-height-md);
|
|
61
|
+
--al-number-field-font-weight: 400;
|
|
62
|
+
--al-number-field-tracking: 0em;
|
|
63
|
+
--al-number-field-label-font-size: var(--al-font-size-md);
|
|
64
|
+
--al-number-field-label-line-height: var(--al-line-height-md);
|
|
65
|
+
--al-number-field-label-font-weight: 400;
|
|
66
|
+
--al-number-field-label-tracking: 0em;
|
|
67
|
+
--al-number-field-optional-font-size: var(--al-font-size-sm);
|
|
68
|
+
--al-number-field-optional-line-height: var(--al-line-height-sm);
|
|
69
|
+
--al-number-field-optional-font-weight: 400;
|
|
70
|
+
--al-number-field-optional-tracking: 0em;
|
|
71
|
+
--al-number-field-help-font-size: var(--al-font-size-xs);
|
|
72
|
+
--al-number-field-help-line-height: var(--al-line-height-xs);
|
|
73
|
+
--al-number-field-help-font-weight: 400;
|
|
74
|
+
--al-number-field-help-tracking: 0.01em;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* AL Design System - Number field
|
|
78
|
+
*
|
|
79
|
+
* Depends on: foundation/al-foundation.css
|
|
80
|
+
* + components/number-field/al-number-field-tokens.css
|
|
81
|
+
* + components/icon/icon.css
|
|
82
|
+
* + components/number-field/number-field.js (the steppers)
|
|
83
|
+
*
|
|
84
|
+
* A QUANTITY THE PERSON ADJUSTS A LITTLE AT A TIME
|
|
85
|
+
* A native <input type="number"> between a - and a + button. Numbers that
|
|
86
|
+
* are not quantities (zip code, phone, card) are the Input with
|
|
87
|
+
* inputmode="numeric" - rule 2.
|
|
88
|
+
*
|
|
89
|
+
* ANATOMY
|
|
90
|
+
* <div class="al-number-field">
|
|
91
|
+
* <div class="al-number-field__labelrow">
|
|
92
|
+
* <label class="al-number-field__label" for="qty">Quantity</label>
|
|
93
|
+
* <span class="al-number-field__optional">(Optional)</span>
|
|
94
|
+
* </div>
|
|
95
|
+
* <div class="al-number-field__control">
|
|
96
|
+
* <button class="al-number-field__step" type="button" tabindex="-1"
|
|
97
|
+
* data-step="down" aria-controls="qty"
|
|
98
|
+
* aria-label="Decrease quantity" hidden>
|
|
99
|
+
* <svg class="al-icon" aria-hidden="true" focusable="false">minus</svg>
|
|
100
|
+
* </button>
|
|
101
|
+
* <input class="al-number-field__field" id="qty" type="number"
|
|
102
|
+
* min="1" max="10" step="1" value="1"
|
|
103
|
+
* aria-describedby="qty-help">
|
|
104
|
+
* <button class="al-number-field__step" type="button" tabindex="-1"
|
|
105
|
+
* data-step="up" aria-controls="qty"
|
|
106
|
+
* aria-label="Increase quantity" hidden>
|
|
107
|
+
* <svg class="al-icon" aria-hidden="true" focusable="false">plus</svg>
|
|
108
|
+
* </button>
|
|
109
|
+
* </div>
|
|
110
|
+
* <p class="al-number-field__help" id="qty-help">From 1 to 10</p>
|
|
111
|
+
* </div>
|
|
112
|
+
*
|
|
113
|
+
* THE STEPPERS ARE OUT OF THE TAB ORDER
|
|
114
|
+
* One tab stop: the arrow keys and typing change the value (Carbon, rule
|
|
115
|
+
* 13). So the buttons carry tabindex="-1", have no ring and no hover of
|
|
116
|
+
* their own (rule 14) - the field's border and ring say where pointer and
|
|
117
|
+
* focus are. They are born `hidden`: without JavaScript the arrow keys
|
|
118
|
+
* still work and a button that does nothing never shows (the same as the
|
|
119
|
+
* Password's eye).
|
|
120
|
+
*
|
|
121
|
+
* THE STEPPER AT THE LIMIT IS A DISABLED <button>
|
|
122
|
+
* At the minimum the - is `disabled`, at the maximum the + (rule 10). The
|
|
123
|
+
* JS sets the attribute; the CSS only reads `:disabled`. There is no Figma
|
|
124
|
+
* variant for it.
|
|
125
|
+
*
|
|
126
|
+
* THE BOX IS `__control`, NOT THE <input>
|
|
127
|
+
* The same as the Input: border, background, radius and ring live on
|
|
128
|
+
* `__control` and the state is read through `:has()`. Without `:has()` the
|
|
129
|
+
* native outline stays (it is only removed inside @supports).
|
|
130
|
+
*
|
|
131
|
+
* THE STATE IS DRIVEN BY THE MARKUP
|
|
132
|
+
* Error reads `aria-invalid="true"`; disabled `:disabled`; read-only
|
|
133
|
+
* `[readonly]` - never `:read-only`, which also matches disabled. The same
|
|
134
|
+
* precedence by variable as the Input: error, read-only, disabled in file
|
|
135
|
+
* order, disabled wins over all.
|
|
136
|
+
*
|
|
137
|
+
* READ-ONLY HAS NO STEPPERS (rule 17)
|
|
138
|
+
* Nothing can change, so they are hidden and the value stays centered.
|
|
139
|
+
*
|
|
140
|
+
* THE BROWSER'S OWN SPINNERS ARE HIDDEN
|
|
141
|
+
* WebKit draws arrows inside type=number and Firefox draws its own. With
|
|
142
|
+
* AL's buttons there would be two pairs.
|
|
143
|
+
*
|
|
144
|
+
* HEIGHT AND WIDTH
|
|
145
|
+
* 48 = padding-y x2 + line height, border subtracted from the padding.
|
|
146
|
+
* The box is fixed at `--al-number-field-width` (140), decided on
|
|
147
|
+
* 2026-10-21. Label and help text are not limited by it - they may be
|
|
148
|
+
* wider than the box.
|
|
149
|
+
*
|
|
150
|
+
* HANDWRITTEN. The tokens it consumes are generated by tokens.py.
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
/* ---------------------------------------------------------------- root */
|
|
154
|
+
.al-number-field {
|
|
155
|
+
display: flex;
|
|
156
|
+
flex-direction: column;
|
|
157
|
+
gap: var(--al-number-field-gap);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* ------------------------------------------------------------ label */
|
|
161
|
+
.al-number-field__labelrow {
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: baseline;
|
|
164
|
+
gap: var(--al-number-field-gap);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.al-number-field__label {
|
|
168
|
+
color: var(--al-number-field-label);
|
|
169
|
+
font-family: var(--al-font-sans);
|
|
170
|
+
font-size: var(--al-number-field-label-font-size);
|
|
171
|
+
line-height: var(--al-number-field-label-line-height);
|
|
172
|
+
font-weight: var(--al-number-field-label-font-weight);
|
|
173
|
+
letter-spacing: var(--al-number-field-label-tracking);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.al-number-field__optional {
|
|
177
|
+
color: var(--al-number-field-optional);
|
|
178
|
+
font-family: var(--al-font-sans);
|
|
179
|
+
font-size: var(--al-number-field-optional-font-size);
|
|
180
|
+
line-height: var(--al-number-field-optional-line-height);
|
|
181
|
+
font-weight: var(--al-number-field-optional-font-weight);
|
|
182
|
+
letter-spacing: var(--al-number-field-optional-tracking);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* --------------------------------------------------------------- box */
|
|
186
|
+
.al-number-field__control {
|
|
187
|
+
--_border-state: var(--al-number-field-border);
|
|
188
|
+
|
|
189
|
+
box-sizing: border-box;
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
gap: var(--al-number-field-stepper-gap);
|
|
193
|
+
width: var(--al-number-field-width);
|
|
194
|
+
|
|
195
|
+
padding: calc(var(--al-number-field-padding-y) - var(--al-number-field-border-width))
|
|
196
|
+
calc(var(--al-number-field-padding-x) - var(--al-number-field-border-width));
|
|
197
|
+
|
|
198
|
+
border: var(--al-number-field-border-width) solid
|
|
199
|
+
var(--_border-force, var(--_border-state));
|
|
200
|
+
border-radius: var(--al-number-field-radius);
|
|
201
|
+
background-color: var(--_bg-force, var(--al-number-field-bg));
|
|
202
|
+
cursor: text;
|
|
203
|
+
|
|
204
|
+
transition:
|
|
205
|
+
border-color var(--al-motion-duration-feedback) var(--al-motion-easing-exit),
|
|
206
|
+
box-shadow var(--al-motion-duration-feedback) var(--al-motion-easing-exit);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* ------------------------------------------------------------- field */
|
|
210
|
+
.al-number-field__field {
|
|
211
|
+
--_text-state: var(--al-number-field-text-value);
|
|
212
|
+
|
|
213
|
+
flex: 1 1 0;
|
|
214
|
+
min-width: 0;
|
|
215
|
+
width: 100%;
|
|
216
|
+
margin: 0;
|
|
217
|
+
padding: 0;
|
|
218
|
+
border: 0;
|
|
219
|
+
background: none;
|
|
220
|
+
color: var(--_text-force, var(--_text-state));
|
|
221
|
+
text-align: center;
|
|
222
|
+
|
|
223
|
+
font-family: var(--al-font-sans);
|
|
224
|
+
font-size: var(--al-number-field-font-size);
|
|
225
|
+
line-height: var(--al-number-field-line-height);
|
|
226
|
+
font-weight: var(--al-number-field-font-weight);
|
|
227
|
+
letter-spacing: var(--al-number-field-tracking);
|
|
228
|
+
font-variant-numeric: tabular-nums;
|
|
229
|
+
|
|
230
|
+
-moz-appearance: textfield;
|
|
231
|
+
appearance: textfield;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* the browser's own spinners - see the header */
|
|
235
|
+
.al-number-field__field::-webkit-inner-spin-button,
|
|
236
|
+
.al-number-field__field::-webkit-outer-spin-button {
|
|
237
|
+
-webkit-appearance: none;
|
|
238
|
+
margin: 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
@supports selector(:has(*)) {
|
|
242
|
+
.al-number-field__field:focus {
|
|
243
|
+
outline: none;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* --------------------------------------------------------- the steppers */
|
|
248
|
+
.al-number-field__step {
|
|
249
|
+
display: inline-flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
justify-content: center;
|
|
252
|
+
flex: none;
|
|
253
|
+
|
|
254
|
+
box-sizing: border-box;
|
|
255
|
+
width: var(--al-number-field-icon-size);
|
|
256
|
+
height: var(--al-number-field-icon-size);
|
|
257
|
+
margin: 0;
|
|
258
|
+
padding: 0;
|
|
259
|
+
border: 0;
|
|
260
|
+
background: none;
|
|
261
|
+
/* the svg inherits from here through currentColor - the Icon contract */
|
|
262
|
+
color: var(--al-number-field-stepper-icon);
|
|
263
|
+
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
-webkit-appearance: none;
|
|
266
|
+
appearance: none;
|
|
267
|
+
-webkit-tap-highlight-color: transparent;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* `display` above would beat the attribute - without JS the buttons don't show */
|
|
271
|
+
.al-number-field__step[hidden] {
|
|
272
|
+
display: none;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.al-number-field__step > svg {
|
|
276
|
+
display: block;
|
|
277
|
+
flex: none;
|
|
278
|
+
width: var(--al-number-field-icon-size);
|
|
279
|
+
height: var(--al-number-field-icon-size);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* at the limit, and in a disabled field - rule 10 */
|
|
283
|
+
.al-number-field__step:disabled {
|
|
284
|
+
color: var(--al-number-field-stepper-icon-disabled);
|
|
285
|
+
cursor: not-allowed;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* ------------------------------------------------------------ help text */
|
|
289
|
+
.al-number-field__help {
|
|
290
|
+
color: var(--al-number-field-help);
|
|
291
|
+
font-family: var(--al-font-sans);
|
|
292
|
+
font-size: var(--al-number-field-help-font-size);
|
|
293
|
+
line-height: var(--al-number-field-help-line-height);
|
|
294
|
+
font-weight: var(--al-number-field-help-font-weight);
|
|
295
|
+
letter-spacing: var(--al-number-field-help-tracking);
|
|
296
|
+
margin: 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* -------------------------------------------------------------- states */
|
|
300
|
+
.al-number-field__control:hover:not(:has(.al-number-field__field:disabled, .al-number-field__field[readonly])) {
|
|
301
|
+
--_border-state: var(--al-number-field-border-hover);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.al-number-field__control:has(.al-number-field__field:focus-visible) {
|
|
305
|
+
--_border-state: var(--al-number-field-border-focus);
|
|
306
|
+
box-shadow: var(--_ring-force, var(--al-number-field-ring));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* error - beats hover and focus by writing to another property */
|
|
310
|
+
.al-number-field__control:has(.al-number-field__field[aria-invalid="true"]) {
|
|
311
|
+
--_border-force: var(--al-number-field-border-error);
|
|
312
|
+
--_ring-force: var(--al-number-field-ring-error);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.al-number-field:has(.al-number-field__field[aria-invalid="true"]) .al-number-field__label {
|
|
316
|
+
color: var(--al-number-field-label-error);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.al-number-field:has(.al-number-field__field[aria-invalid="true"]) .al-number-field__help {
|
|
320
|
+
color: var(--al-number-field-help-error);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/* read-only - `[readonly]`, never `:read-only`. No steppers - rule 17. */
|
|
324
|
+
.al-number-field__control:has(.al-number-field__field[readonly]) {
|
|
325
|
+
--_border-force: var(--al-number-field-border-readonly);
|
|
326
|
+
--_bg-force: var(--al-number-field-bg-readonly);
|
|
327
|
+
cursor: default;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.al-number-field__control:has(.al-number-field__field[readonly]) .al-number-field__step {
|
|
331
|
+
display: none;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* disabled - last, because it wins over all */
|
|
335
|
+
.al-number-field__control:has(.al-number-field__field:disabled) {
|
|
336
|
+
--_border-force: var(--al-number-field-border-disabled);
|
|
337
|
+
--_bg-force: var(--al-number-field-bg-disabled);
|
|
338
|
+
cursor: not-allowed;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.al-number-field__field:disabled {
|
|
342
|
+
--_text-force: var(--al-number-field-text-disabled);
|
|
343
|
+
cursor: not-allowed;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.al-number-field:has(.al-number-field__field:disabled) .al-number-field__label,
|
|
347
|
+
.al-number-field:has(.al-number-field__field:disabled) .al-number-field__optional {
|
|
348
|
+
color: var(--al-number-field-label-disabled);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* ENTER - CSS uses the curve of the state the transition goes to. */
|
|
352
|
+
.al-number-field__control:hover,
|
|
353
|
+
.al-number-field__control:has(.al-number-field__field:focus-visible),
|
|
354
|
+
.al-number-field__control:has(.al-number-field__field[aria-invalid="true"]) {
|
|
355
|
+
transition-timing-function: var(--al-motion-easing-enter);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* --------------------------------------------------------- environment */
|
|
359
|
+
@media (prefers-reduced-motion: reduce) {
|
|
360
|
+
.al-number-field__control {
|
|
361
|
+
transition: none;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* AL Design System - Number field: the steppers' behavior
|
|
2
|
+
*
|
|
3
|
+
* The arrow keys and typing are native to <input type="number">. This file
|
|
4
|
+
* only does what the browser doesn't:
|
|
5
|
+
*
|
|
6
|
+
* 1. removes the buttons' `hidden` (without JS they don't exist);
|
|
7
|
+
* 2. - and + call stepDown()/stepUp() and fire `input` and `change`, so the
|
|
8
|
+
* form hears the change like it was typed. One click is one step:
|
|
9
|
+
* holding the button does NOT repeat (decision E=a);
|
|
10
|
+
* 3. keeps focus where it was - clicking a stepper doesn't move focus to
|
|
11
|
+
* the field or to the button (rule 15);
|
|
12
|
+
* 4. disables the stepper at the limit: - at the minimum, + at the maximum
|
|
13
|
+
* (rule 10). A disabled field disables both.
|
|
14
|
+
*
|
|
15
|
+
* It never clamps what was typed (decision F=a): a value out of range stays
|
|
16
|
+
* as typed, and the form shows the error by reading `field.validity`
|
|
17
|
+
* (rangeUnderflow / rangeOverflow) and setting aria-invalid + the message.
|
|
18
|
+
*
|
|
19
|
+
* It starts by itself on load. Content inserted later:
|
|
20
|
+
* window.alNumberField.init(elementContainingTheFields)
|
|
21
|
+
*/
|
|
22
|
+
(function () {
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
function setup(root) {
|
|
26
|
+
var field = root.querySelector('.al-number-field__field');
|
|
27
|
+
var down = root.querySelector('.al-number-field__step[data-step="down"]');
|
|
28
|
+
var up = root.querySelector('.al-number-field__step[data-step="up"]');
|
|
29
|
+
if (!field || !down || !up || root.hasAttribute('data-al-ready')) return;
|
|
30
|
+
root.setAttribute('data-al-ready', '');
|
|
31
|
+
|
|
32
|
+
function bound(attr) {
|
|
33
|
+
var v = field.getAttribute(attr);
|
|
34
|
+
return v === null || v === '' ? null : Number(v);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sync() {
|
|
38
|
+
var value = field.valueAsNumber;
|
|
39
|
+
var min = bound('min');
|
|
40
|
+
var max = bound('max');
|
|
41
|
+
var off = field.disabled || field.readOnly;
|
|
42
|
+
down.disabled = off || (min !== null && !isNaN(value) && value <= min);
|
|
43
|
+
up.disabled = off || (max !== null && !isNaN(value) && value >= max);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function step(dir) {
|
|
47
|
+
if (field.disabled || field.readOnly) return;
|
|
48
|
+
try {
|
|
49
|
+
if (dir === 'up') field.stepUp(); else field.stepDown();
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return; // an empty or invalid value - stepping has nothing to start from
|
|
52
|
+
}
|
|
53
|
+
field.dispatchEvent(new Event('input', { bubbles: true }));
|
|
54
|
+
field.dispatchEvent(new Event('change', { bubbles: true }));
|
|
55
|
+
sync();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[down, up].forEach(function (button) {
|
|
59
|
+
// rule 15: the click must not take focus from where it was
|
|
60
|
+
button.addEventListener('mousedown', function (e) { e.preventDefault(); });
|
|
61
|
+
button.addEventListener('click', function () {
|
|
62
|
+
step(button.getAttribute('data-step'));
|
|
63
|
+
});
|
|
64
|
+
button.hidden = false;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
field.addEventListener('input', sync);
|
|
68
|
+
sync();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function init(scope) {
|
|
72
|
+
var roots = (scope || document).querySelectorAll('.al-number-field');
|
|
73
|
+
for (var i = 0; i < roots.length; i++) setup(roots[i]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
window.alNumberField = { init: init };
|
|
77
|
+
|
|
78
|
+
if (document.readyState === 'loading') {
|
|
79
|
+
document.addEventListener('DOMContentLoaded', function () { init(); });
|
|
80
|
+
} else {
|
|
81
|
+
init();
|
|
82
|
+
}
|
|
83
|
+
})();
|
package/dist/foundation.css
CHANGED
package/dist/tokens.json
CHANGED