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 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 + 26 components**, designed in Figma and coded from the same tokens.
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.3.0/dist/al.css">
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 26 components, in one file |
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 9 components that have a script |
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 + 26 componentes**, desenhados no Figma e codados a partir dos mesmos tokens.
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.3.0/dist/al.css">
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 26 componentes, num arquivo só |
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 9 componentes que têm script |
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.3.0 - Foundation + 26 components.
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.3.0 - MIT
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
+ })();
@@ -1,5 +1,5 @@
1
1
  /* AL Design System - Foundation
2
- * v1.3.0 - MIT
2
+ * v1.4.0 - MIT
3
3
  * GENERATED by src/foundation/css.py. Do not edit by hand: run the script.
4
4
  */
5
5
 
package/dist/tokens.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "name": "AL Design System",
4
- "version": "1.3.0",
4
+ "version": "1.4.0",
5
5
  "license": "MIT",
6
6
  "brandAnchor": "#FC5000",
7
7
  "colorSpace": "OKLCH",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "al-design-system",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "AL Design System: Foundation and components in plain CSS, from Figma to code.",
5
5
  "license": "MIT",
6
6
  "author": "Guilherme Domingues",