al-design-system 1.8.0 → 1.9.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 +172 -2
- package/dist/components/carousel.css +169 -0
- package/dist/components/carousel.js +137 -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 + 33 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.
|
|
@@ -44,6 +44,7 @@ An open-source design system, from Figma to code. Plain CSS, a little vanilla Ja
|
|
|
44
44
|
| | Drawer | ✓ | [guidelines](src/components/drawer/guidelines.md) |
|
|
45
45
|
| Navigation | Sidebar | ✓ | [guidelines](src/components/sidebar/guidelines.md) |
|
|
46
46
|
| | Breadcrumb | ✓ | [guidelines](src/components/breadcrumb/guidelines.md) |
|
|
47
|
+
| | Carousel | ✓ | [guidelines](src/components/carousel/guidelines.md) |
|
|
47
48
|
| | Pagination | | [guidelines](src/components/pagination/guidelines.md) |
|
|
48
49
|
| | Stepper | | [guidelines](src/components/stepper/guidelines.md) |
|
|
49
50
|
| Feedback | Tooltip | ✓ | [guidelines](src/components/tooltip/guidelines.md) |
|
|
@@ -63,7 +64,7 @@ npm install al-design-system
|
|
|
63
64
|
Or straight from a CDN, pinned to a version:
|
|
64
65
|
|
|
65
66
|
```html
|
|
66
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.
|
|
67
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.9.0/dist/al.css">
|
|
67
68
|
```
|
|
68
69
|
|
|
69
70
|
Each [GitHub Release](https://github.com/guilhermedworakowski/al-design-system/releases) also carries the `dist/` folder as a zip.
|
|
@@ -72,10 +73,10 @@ Each [GitHub Release](https://github.com/guilhermedworakowski/al-design-system/r
|
|
|
72
73
|
|
|
73
74
|
| File | What it is |
|
|
74
75
|
|---|---|
|
|
75
|
-
| `al.css` | Foundation + all
|
|
76
|
+
| `al.css` | Foundation + all 33 components, in one file |
|
|
76
77
|
| `foundation.css` | Only the Foundation: color, typography, spacing, radius, icon size, elevation, focus ring and motion |
|
|
77
78
|
| `components/<name>.css` | One component (its tokens + its CSS) |
|
|
78
|
-
| `components/<name>.js` | The behavior, for the
|
|
79
|
+
| `components/<name>.js` | The behavior, for the 13 components that have a script |
|
|
79
80
|
| `icons/*.svg`, `icons/icons.json` | The 70 icons |
|
|
80
81
|
| `tokens.json` | The Foundation tokens, to read from code |
|
|
81
82
|
|
|
@@ -106,7 +107,7 @@ The exact markup of each component (which element, which attributes) is describe
|
|
|
106
107
|
|
|
107
108
|
### Scripts
|
|
108
109
|
|
|
109
|
-
Password, Number field, Slider, Menu, 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:
|
|
110
|
+
Password, Number field, Slider, Menu, Tab, Modal, Drawer, Sidebar, Breadcrumb, Carousel, Tooltip, Toast and Alert need their script, loaded once per page. Each one wires up the components already on the page by itself:
|
|
110
111
|
|
|
111
112
|
```html
|
|
112
113
|
<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 + 33 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ó.
|
|
@@ -44,6 +44,7 @@ Design system open source, do Figma ao código. CSS puro, um pouco de JavaScript
|
|
|
44
44
|
| | Drawer | ✓ | [diretrizes](src/components/drawer/guidelines.md) |
|
|
45
45
|
| Navegação | Sidebar | ✓ | [diretrizes](src/components/sidebar/guidelines.md) |
|
|
46
46
|
| | Breadcrumb | ✓ | [diretrizes](src/components/breadcrumb/guidelines.md) |
|
|
47
|
+
| | Carousel | ✓ | [diretrizes](src/components/carousel/guidelines.md) |
|
|
47
48
|
| | Pagination | | [diretrizes](src/components/pagination/guidelines.md) |
|
|
48
49
|
| | Stepper | | [diretrizes](src/components/stepper/guidelines.md) |
|
|
49
50
|
| Feedback | Tooltip | ✓ | [diretrizes](src/components/tooltip/guidelines.md) |
|
|
@@ -63,7 +64,7 @@ npm install al-design-system
|
|
|
63
64
|
Ou direto de uma CDN, fixado numa versão:
|
|
64
65
|
|
|
65
66
|
```html
|
|
66
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.
|
|
67
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.9.0/dist/al.css">
|
|
67
68
|
```
|
|
68
69
|
|
|
69
70
|
Cada [Release no GitHub](https://github.com/guilhermedworakowski/al-design-system/releases) também traz a pasta `dist/` num zip.
|
|
@@ -72,10 +73,10 @@ Cada [Release no GitHub](https://github.com/guilhermedworakowski/al-design-syste
|
|
|
72
73
|
|
|
73
74
|
| Arquivo | O que é |
|
|
74
75
|
|---|---|
|
|
75
|
-
| `al.css` | Foundation + os
|
|
76
|
+
| `al.css` | Foundation + os 33 componentes, num arquivo só |
|
|
76
77
|
| `foundation.css` | Só a Foundation: cor, tipografia, espaçamento, radius, tamanho de ícone, elevação, anel de foco e motion |
|
|
77
78
|
| `components/<nome>.css` | Um componente (os tokens + o CSS dele) |
|
|
78
|
-
| `components/<nome>.js` | O comportamento, para os
|
|
79
|
+
| `components/<nome>.js` | O comportamento, para os 13 componentes que têm script |
|
|
79
80
|
| `icons/*.svg`, `icons/icons.json` | Os 70 ícones |
|
|
80
81
|
| `tokens.json` | Os tokens da Foundation, para ler por código |
|
|
81
82
|
|
|
@@ -106,7 +107,7 @@ A marcação exata de cada componente (qual elemento, quais atributos) está des
|
|
|
106
107
|
|
|
107
108
|
### Scripts
|
|
108
109
|
|
|
109
|
-
Password, Number field, Slider, Menu, 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:
|
|
110
|
+
Password, Number field, Slider, Menu, Tab, Modal, Drawer, Sidebar, Breadcrumb, Carousel, 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:
|
|
110
111
|
|
|
111
112
|
```html
|
|
112
113
|
<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.9.0 - Foundation + 33 components.
|
|
2
2
|
* MIT. https://github.com/guilhermedworakowski/al-design-system
|
|
3
3
|
*/
|
|
4
4
|
/* AL Design System - Foundation
|
|
5
|
-
* v1.
|
|
5
|
+
* v1.9.0 - MIT
|
|
6
6
|
* GENERATED by src/foundation/css.py. Do not edit by hand: run the script.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -8679,3 +8679,173 @@
|
|
|
8679
8679
|
@media (prefers-reduced-motion: reduce) {
|
|
8680
8680
|
.al-rating__glyph { transition: none; }
|
|
8681
8681
|
}
|
|
8682
|
+
|
|
8683
|
+
/* AL Design System - Carousel tokens
|
|
8684
|
+
* GENERATED by src/components/carousel/tokens.py. Do not edit by hand.
|
|
8685
|
+
*
|
|
8686
|
+
* Every color is a semantic the theme switches on :root.
|
|
8687
|
+
*/
|
|
8688
|
+
|
|
8689
|
+
:root {
|
|
8690
|
+
/* color */
|
|
8691
|
+
--al-carousel-dot: var(--al-border-strong);
|
|
8692
|
+
--al-carousel-dot-current: var(--al-bg-brand);
|
|
8693
|
+
--al-carousel-slide-bg: var(--al-bg-subtle);
|
|
8694
|
+
|
|
8695
|
+
/* geometry */
|
|
8696
|
+
--al-carousel-gap: var(--al-space-16);
|
|
8697
|
+
--al-carousel-controls-gap: var(--al-space-16);
|
|
8698
|
+
--al-carousel-nav-gap: var(--al-space-8);
|
|
8699
|
+
--al-carousel-dot-size: var(--al-space-8);
|
|
8700
|
+
--al-carousel-dot-current-width: var(--al-space-24);
|
|
8701
|
+
--al-carousel-dot-gap: var(--al-space-8);
|
|
8702
|
+
--al-carousel-dot-radius: var(--al-radius-full);
|
|
8703
|
+
--al-carousel-slide-radius: var(--al-radius-lg);
|
|
8704
|
+
}
|
|
8705
|
+
|
|
8706
|
+
/* AL Design System - Carousel
|
|
8707
|
+
*
|
|
8708
|
+
* Depends on: al-foundation.css, al-carousel-tokens.css, and the Icon Button
|
|
8709
|
+
* (al-icon-tokens.css + icon.css + al-icon-button-tokens.css + icon-button.css)
|
|
8710
|
+
* for the arrows. Behavior in carousel.js.
|
|
8711
|
+
*
|
|
8712
|
+
* The basic carousel of the APG (usage rules 13-23): previous and next below
|
|
8713
|
+
* the content, disabled at the ends, no loop, no auto-rotation. The dots are
|
|
8714
|
+
* decorative.
|
|
8715
|
+
*
|
|
8716
|
+
* <section class="al-carousel" aria-roledescription="carousel" aria-labelledby="t">
|
|
8717
|
+
* <h2 id="t">Visible title</h2> (rule 9 - the page's heading)
|
|
8718
|
+
* <div class="al-carousel__viewport">
|
|
8719
|
+
* <div class="al-carousel__slide" role="group"
|
|
8720
|
+
* aria-roledescription="slide" aria-label="1 of 5">…</div>
|
|
8721
|
+
* …
|
|
8722
|
+
* </div>
|
|
8723
|
+
* <div class="al-carousel__controls">
|
|
8724
|
+
* <span class="al-carousel__dots" aria-hidden="true">
|
|
8725
|
+
* <span class="al-carousel__dot"></span>… (one per slide, carousel.js marks the current)
|
|
8726
|
+
* </span>
|
|
8727
|
+
* <div class="al-carousel__nav">
|
|
8728
|
+
* <button type="button" class="al-icon-btn al-icon-btn--secondary al-icon-btn--sm"
|
|
8729
|
+
* data-al-carousel="prev" aria-label="Previous slide">…chevron-left…</button>
|
|
8730
|
+
* <button type="button" class="al-icon-btn al-icon-btn--secondary al-icon-btn--sm"
|
|
8731
|
+
* data-al-carousel="next" aria-label="Next slide">…chevron-right…</button>
|
|
8732
|
+
* </div>
|
|
8733
|
+
* </div>
|
|
8734
|
+
* <p class="al-carousel__live" aria-live="polite" aria-atomic="true"></p>
|
|
8735
|
+
* </section>
|
|
8736
|
+
*
|
|
8737
|
+
* SLIDE WIDTH IS THE LAYOUT'S (rule 6): --al-carousel-slide-size is a public
|
|
8738
|
+
* adjustment point, not a token. Default 100% = one slide per view; set it
|
|
8739
|
+
* narrower than the viewport so the next slide shows cut at the edge.
|
|
8740
|
+
*
|
|
8741
|
+
* NO JS NEEDED FOR FOCUS (rule 23): focusing a link inside a slide out of
|
|
8742
|
+
* view scrolls the viewport natively.
|
|
8743
|
+
*
|
|
8744
|
+
* The viewport hides its scrollbar: the arrows, swipe and the trackpad move
|
|
8745
|
+
* it. Smooth scrolling is turned off under prefers-reduced-motion (rule 16).
|
|
8746
|
+
*/
|
|
8747
|
+
|
|
8748
|
+
.al-carousel {
|
|
8749
|
+
--al-carousel-slide-size: 100%;
|
|
8750
|
+
display: flex;
|
|
8751
|
+
flex-direction: column;
|
|
8752
|
+
gap: var(--al-carousel-controls-gap);
|
|
8753
|
+
min-inline-size: 0;
|
|
8754
|
+
}
|
|
8755
|
+
|
|
8756
|
+
/* -------------------------------------------------------------- viewport */
|
|
8757
|
+
.al-carousel__viewport {
|
|
8758
|
+
display: flex;
|
|
8759
|
+
gap: var(--al-carousel-gap);
|
|
8760
|
+
overflow-x: auto;
|
|
8761
|
+
overscroll-behavior-x: contain;
|
|
8762
|
+
scroll-snap-type: x mandatory;
|
|
8763
|
+
scroll-behavior: smooth;
|
|
8764
|
+
scrollbar-width: none;
|
|
8765
|
+
}
|
|
8766
|
+
|
|
8767
|
+
.al-carousel__viewport::-webkit-scrollbar {
|
|
8768
|
+
display: none;
|
|
8769
|
+
}
|
|
8770
|
+
|
|
8771
|
+
.al-carousel__slide {
|
|
8772
|
+
flex: 0 0 var(--al-carousel-slide-size);
|
|
8773
|
+
scroll-snap-align: start;
|
|
8774
|
+
overflow: hidden;
|
|
8775
|
+
border-radius: var(--al-carousel-slide-radius);
|
|
8776
|
+
background-color: var(--al-carousel-slide-bg); /* rule 18 - shows only when the image is missing */
|
|
8777
|
+
}
|
|
8778
|
+
|
|
8779
|
+
.al-carousel__slide > img {
|
|
8780
|
+
display: block;
|
|
8781
|
+
inline-size: 100%;
|
|
8782
|
+
block-size: 100%;
|
|
8783
|
+
object-fit: cover;
|
|
8784
|
+
}
|
|
8785
|
+
|
|
8786
|
+
/* -------------------------------------------------------------- controls */
|
|
8787
|
+
.al-carousel__controls {
|
|
8788
|
+
display: flex;
|
|
8789
|
+
align-items: center;
|
|
8790
|
+
justify-content: space-between;
|
|
8791
|
+
}
|
|
8792
|
+
|
|
8793
|
+
.al-carousel__nav {
|
|
8794
|
+
display: flex;
|
|
8795
|
+
gap: var(--al-carousel-nav-gap);
|
|
8796
|
+
margin-inline-start: auto;
|
|
8797
|
+
}
|
|
8798
|
+
|
|
8799
|
+
/* the dots: decorative, shape carries the current one (rule 22) */
|
|
8800
|
+
.al-carousel__dots {
|
|
8801
|
+
display: flex;
|
|
8802
|
+
align-items: center;
|
|
8803
|
+
gap: var(--al-carousel-dot-gap);
|
|
8804
|
+
}
|
|
8805
|
+
|
|
8806
|
+
.al-carousel__dot {
|
|
8807
|
+
inline-size: var(--al-carousel-dot-size);
|
|
8808
|
+
block-size: var(--al-carousel-dot-size);
|
|
8809
|
+
border-radius: var(--al-carousel-dot-radius);
|
|
8810
|
+
background-color: var(--al-carousel-dot);
|
|
8811
|
+
transition:
|
|
8812
|
+
inline-size var(--al-motion-duration-feedback) var(--al-motion-easing-exit),
|
|
8813
|
+
background-color var(--al-motion-duration-feedback) var(--al-motion-easing-exit);
|
|
8814
|
+
}
|
|
8815
|
+
|
|
8816
|
+
.al-carousel__dot[data-current] {
|
|
8817
|
+
inline-size: var(--al-carousel-dot-current-width);
|
|
8818
|
+
background-color: var(--al-carousel-dot-current);
|
|
8819
|
+
transition-timing-function: var(--al-motion-easing-enter);
|
|
8820
|
+
}
|
|
8821
|
+
|
|
8822
|
+
/* the polite region that announces the position after an arrow (rule 21) */
|
|
8823
|
+
.al-carousel__live {
|
|
8824
|
+
position: absolute;
|
|
8825
|
+
inline-size: var(--al-border-width-1);
|
|
8826
|
+
block-size: var(--al-border-width-1);
|
|
8827
|
+
margin: 0;
|
|
8828
|
+
overflow: hidden;
|
|
8829
|
+
clip-path: inset(50%);
|
|
8830
|
+
white-space: nowrap;
|
|
8831
|
+
}
|
|
8832
|
+
|
|
8833
|
+
@media (prefers-reduced-motion: reduce) {
|
|
8834
|
+
.al-carousel__viewport {
|
|
8835
|
+
scroll-behavior: auto;
|
|
8836
|
+
}
|
|
8837
|
+
|
|
8838
|
+
.al-carousel__dot {
|
|
8839
|
+
transition: none;
|
|
8840
|
+
}
|
|
8841
|
+
}
|
|
8842
|
+
|
|
8843
|
+
@media (forced-colors: active) {
|
|
8844
|
+
.al-carousel__dot {
|
|
8845
|
+
background-color: CanvasText;
|
|
8846
|
+
}
|
|
8847
|
+
|
|
8848
|
+
.al-carousel__dot[data-current] {
|
|
8849
|
+
background-color: Highlight;
|
|
8850
|
+
}
|
|
8851
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/* AL Design System - Carousel tokens
|
|
2
|
+
* GENERATED by src/components/carousel/tokens.py. Do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Every color is a semantic the theme switches on :root.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
:root {
|
|
8
|
+
/* color */
|
|
9
|
+
--al-carousel-dot: var(--al-border-strong);
|
|
10
|
+
--al-carousel-dot-current: var(--al-bg-brand);
|
|
11
|
+
--al-carousel-slide-bg: var(--al-bg-subtle);
|
|
12
|
+
|
|
13
|
+
/* geometry */
|
|
14
|
+
--al-carousel-gap: var(--al-space-16);
|
|
15
|
+
--al-carousel-controls-gap: var(--al-space-16);
|
|
16
|
+
--al-carousel-nav-gap: var(--al-space-8);
|
|
17
|
+
--al-carousel-dot-size: var(--al-space-8);
|
|
18
|
+
--al-carousel-dot-current-width: var(--al-space-24);
|
|
19
|
+
--al-carousel-dot-gap: var(--al-space-8);
|
|
20
|
+
--al-carousel-dot-radius: var(--al-radius-full);
|
|
21
|
+
--al-carousel-slide-radius: var(--al-radius-lg);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* AL Design System - Carousel
|
|
25
|
+
*
|
|
26
|
+
* Depends on: al-foundation.css, al-carousel-tokens.css, and the Icon Button
|
|
27
|
+
* (al-icon-tokens.css + icon.css + al-icon-button-tokens.css + icon-button.css)
|
|
28
|
+
* for the arrows. Behavior in carousel.js.
|
|
29
|
+
*
|
|
30
|
+
* The basic carousel of the APG (usage rules 13-23): previous and next below
|
|
31
|
+
* the content, disabled at the ends, no loop, no auto-rotation. The dots are
|
|
32
|
+
* decorative.
|
|
33
|
+
*
|
|
34
|
+
* <section class="al-carousel" aria-roledescription="carousel" aria-labelledby="t">
|
|
35
|
+
* <h2 id="t">Visible title</h2> (rule 9 - the page's heading)
|
|
36
|
+
* <div class="al-carousel__viewport">
|
|
37
|
+
* <div class="al-carousel__slide" role="group"
|
|
38
|
+
* aria-roledescription="slide" aria-label="1 of 5">…</div>
|
|
39
|
+
* …
|
|
40
|
+
* </div>
|
|
41
|
+
* <div class="al-carousel__controls">
|
|
42
|
+
* <span class="al-carousel__dots" aria-hidden="true">
|
|
43
|
+
* <span class="al-carousel__dot"></span>… (one per slide, carousel.js marks the current)
|
|
44
|
+
* </span>
|
|
45
|
+
* <div class="al-carousel__nav">
|
|
46
|
+
* <button type="button" class="al-icon-btn al-icon-btn--secondary al-icon-btn--sm"
|
|
47
|
+
* data-al-carousel="prev" aria-label="Previous slide">…chevron-left…</button>
|
|
48
|
+
* <button type="button" class="al-icon-btn al-icon-btn--secondary al-icon-btn--sm"
|
|
49
|
+
* data-al-carousel="next" aria-label="Next slide">…chevron-right…</button>
|
|
50
|
+
* </div>
|
|
51
|
+
* </div>
|
|
52
|
+
* <p class="al-carousel__live" aria-live="polite" aria-atomic="true"></p>
|
|
53
|
+
* </section>
|
|
54
|
+
*
|
|
55
|
+
* SLIDE WIDTH IS THE LAYOUT'S (rule 6): --al-carousel-slide-size is a public
|
|
56
|
+
* adjustment point, not a token. Default 100% = one slide per view; set it
|
|
57
|
+
* narrower than the viewport so the next slide shows cut at the edge.
|
|
58
|
+
*
|
|
59
|
+
* NO JS NEEDED FOR FOCUS (rule 23): focusing a link inside a slide out of
|
|
60
|
+
* view scrolls the viewport natively.
|
|
61
|
+
*
|
|
62
|
+
* The viewport hides its scrollbar: the arrows, swipe and the trackpad move
|
|
63
|
+
* it. Smooth scrolling is turned off under prefers-reduced-motion (rule 16).
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
.al-carousel {
|
|
67
|
+
--al-carousel-slide-size: 100%;
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
gap: var(--al-carousel-controls-gap);
|
|
71
|
+
min-inline-size: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* -------------------------------------------------------------- viewport */
|
|
75
|
+
.al-carousel__viewport {
|
|
76
|
+
display: flex;
|
|
77
|
+
gap: var(--al-carousel-gap);
|
|
78
|
+
overflow-x: auto;
|
|
79
|
+
overscroll-behavior-x: contain;
|
|
80
|
+
scroll-snap-type: x mandatory;
|
|
81
|
+
scroll-behavior: smooth;
|
|
82
|
+
scrollbar-width: none;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.al-carousel__viewport::-webkit-scrollbar {
|
|
86
|
+
display: none;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.al-carousel__slide {
|
|
90
|
+
flex: 0 0 var(--al-carousel-slide-size);
|
|
91
|
+
scroll-snap-align: start;
|
|
92
|
+
overflow: hidden;
|
|
93
|
+
border-radius: var(--al-carousel-slide-radius);
|
|
94
|
+
background-color: var(--al-carousel-slide-bg); /* rule 18 - shows only when the image is missing */
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.al-carousel__slide > img {
|
|
98
|
+
display: block;
|
|
99
|
+
inline-size: 100%;
|
|
100
|
+
block-size: 100%;
|
|
101
|
+
object-fit: cover;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* -------------------------------------------------------------- controls */
|
|
105
|
+
.al-carousel__controls {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: space-between;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.al-carousel__nav {
|
|
112
|
+
display: flex;
|
|
113
|
+
gap: var(--al-carousel-nav-gap);
|
|
114
|
+
margin-inline-start: auto;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* the dots: decorative, shape carries the current one (rule 22) */
|
|
118
|
+
.al-carousel__dots {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
gap: var(--al-carousel-dot-gap);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.al-carousel__dot {
|
|
125
|
+
inline-size: var(--al-carousel-dot-size);
|
|
126
|
+
block-size: var(--al-carousel-dot-size);
|
|
127
|
+
border-radius: var(--al-carousel-dot-radius);
|
|
128
|
+
background-color: var(--al-carousel-dot);
|
|
129
|
+
transition:
|
|
130
|
+
inline-size var(--al-motion-duration-feedback) var(--al-motion-easing-exit),
|
|
131
|
+
background-color var(--al-motion-duration-feedback) var(--al-motion-easing-exit);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.al-carousel__dot[data-current] {
|
|
135
|
+
inline-size: var(--al-carousel-dot-current-width);
|
|
136
|
+
background-color: var(--al-carousel-dot-current);
|
|
137
|
+
transition-timing-function: var(--al-motion-easing-enter);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* the polite region that announces the position after an arrow (rule 21) */
|
|
141
|
+
.al-carousel__live {
|
|
142
|
+
position: absolute;
|
|
143
|
+
inline-size: var(--al-border-width-1);
|
|
144
|
+
block-size: var(--al-border-width-1);
|
|
145
|
+
margin: 0;
|
|
146
|
+
overflow: hidden;
|
|
147
|
+
clip-path: inset(50%);
|
|
148
|
+
white-space: nowrap;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@media (prefers-reduced-motion: reduce) {
|
|
152
|
+
.al-carousel__viewport {
|
|
153
|
+
scroll-behavior: auto;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.al-carousel__dot {
|
|
157
|
+
transition: none;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@media (forced-colors: active) {
|
|
162
|
+
.al-carousel__dot {
|
|
163
|
+
background-color: CanvasText;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.al-carousel__dot[data-current] {
|
|
167
|
+
background-color: Highlight;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/* AL Design System - Carousel, arrows, dots and announcement
|
|
2
|
+
*
|
|
3
|
+
* What it does on every .al-carousel inside `root` (usage rules 13-22):
|
|
4
|
+
* - previous / next scroll by one page (rule 14, A=a): next starts at the
|
|
5
|
+
* first slide not fully in view, so the one cut at the edge is never
|
|
6
|
+
* skipped; previous goes back as far as one viewport allows;
|
|
7
|
+
* - the arrows get `disabled` at the ends - no loop (rule 13). When the
|
|
8
|
+
* arrow that has focus turns disabled, focus moves to the other one, so
|
|
9
|
+
* it never falls back to <body>;
|
|
10
|
+
* - the dot of the first visible slide gets data-current (rule 22);
|
|
11
|
+
* - only after an ARROW, the live region says which slides are in view
|
|
12
|
+
* (rule 21, B=a). Swipe and trackpad announce nothing. The sentence is
|
|
13
|
+
* data-al-live on the .al-carousel, with {first}, {last} and {total}
|
|
14
|
+
* (default "Slides {first} to {last} of {total}"; one slide in view
|
|
15
|
+
* uses data-al-live-one, default "Slide {first} of {total}").
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* <script src="carousel.js"></script> -> binds itself when the page loads
|
|
19
|
+
* alCarousel.init(container) -> binds content inserted later
|
|
20
|
+
* Binding the same carousel twice duplicates nothing.
|
|
21
|
+
*/
|
|
22
|
+
(function () {
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
function bind(root) {
|
|
26
|
+
if (root.hasAttribute('data-al-carousel-bound')) return;
|
|
27
|
+
var viewport = root.querySelector('.al-carousel__viewport');
|
|
28
|
+
if (!viewport) return;
|
|
29
|
+
root.setAttribute('data-al-carousel-bound', '');
|
|
30
|
+
|
|
31
|
+
var slides = [].slice.call(viewport.querySelectorAll('.al-carousel__slide'));
|
|
32
|
+
var dots = [].slice.call(root.querySelectorAll('.al-carousel__dot'));
|
|
33
|
+
var prev = root.querySelector('[data-al-carousel="prev"]');
|
|
34
|
+
var next = root.querySelector('[data-al-carousel="next"]');
|
|
35
|
+
var live = root.querySelector('.al-carousel__live');
|
|
36
|
+
var announce = false;
|
|
37
|
+
|
|
38
|
+
// slides fully (or almost fully) inside the viewport, 1-based
|
|
39
|
+
function inView() {
|
|
40
|
+
var v = viewport.getBoundingClientRect();
|
|
41
|
+
var first = 0, last = 0;
|
|
42
|
+
slides.forEach(function (s, i) {
|
|
43
|
+
var r = s.getBoundingClientRect();
|
|
44
|
+
if (r.left >= v.left - 1 && r.right <= v.right + 1) {
|
|
45
|
+
if (!first) first = i + 1;
|
|
46
|
+
last = i + 1;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return { first: first || 1, last: last || first || 1 };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function update() {
|
|
53
|
+
var max = viewport.scrollWidth - viewport.clientWidth;
|
|
54
|
+
var atStart = viewport.scrollLeft <= 1;
|
|
55
|
+
var atEnd = viewport.scrollLeft >= max - 1;
|
|
56
|
+
var lost = document.activeElement;
|
|
57
|
+
|
|
58
|
+
if (prev) prev.disabled = atStart;
|
|
59
|
+
if (next) next.disabled = atEnd;
|
|
60
|
+
if (lost === prev && atStart && next && !atEnd) next.focus();
|
|
61
|
+
if (lost === next && atEnd && prev && !atStart) prev.focus();
|
|
62
|
+
|
|
63
|
+
var pos = inView();
|
|
64
|
+
dots.forEach(function (d, i) {
|
|
65
|
+
if (i === pos.first - 1) d.setAttribute('data-current', '');
|
|
66
|
+
else d.removeAttribute('data-current');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (announce && live) {
|
|
70
|
+
var one = pos.first === pos.last;
|
|
71
|
+
var text = one
|
|
72
|
+
? root.getAttribute('data-al-live-one') || 'Slide {first} of {total}'
|
|
73
|
+
: root.getAttribute('data-al-live') || 'Slides {first} to {last} of {total}';
|
|
74
|
+
live.textContent = text
|
|
75
|
+
.replace('{first}', pos.first)
|
|
76
|
+
.replace('{last}', pos.last)
|
|
77
|
+
.replace('{total}', slides.length);
|
|
78
|
+
}
|
|
79
|
+
announce = false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// a slide's start, in the viewport's scroll coordinates
|
|
83
|
+
function start(s) { return s.offsetLeft - slides[0].offsetLeft; }
|
|
84
|
+
|
|
85
|
+
// Next: the first slide not fully in view becomes the first one - a slide
|
|
86
|
+
// cut at the edge is never skipped. Previous: the mirror, the earliest
|
|
87
|
+
// slide that still fits one viewport back.
|
|
88
|
+
function page(dir) {
|
|
89
|
+
announce = true;
|
|
90
|
+
var pos = inView();
|
|
91
|
+
var left;
|
|
92
|
+
if (dir > 0) {
|
|
93
|
+
left = start(slides[Math.min(pos.last, slides.length - 1)]);
|
|
94
|
+
} else {
|
|
95
|
+
var floor = viewport.scrollLeft - viewport.clientWidth;
|
|
96
|
+
var target = slides.filter(function (s) { return start(s) >= floor - 1; })[0];
|
|
97
|
+
left = Math.min(start(target), start(slides[Math.max(pos.first - 2, 0)]));
|
|
98
|
+
}
|
|
99
|
+
viewport.scrollTo({ left: left });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (prev) prev.addEventListener('click', function () { page(-1); });
|
|
103
|
+
if (next) next.addEventListener('click', function () { page(1); });
|
|
104
|
+
|
|
105
|
+
// scrollend where it exists; a short quiet period elsewhere (Safari)
|
|
106
|
+
if ('onscrollend' in window) {
|
|
107
|
+
viewport.addEventListener('scrollend', update);
|
|
108
|
+
} else {
|
|
109
|
+
var timer;
|
|
110
|
+
viewport.addEventListener('scroll', function () {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
timer = setTimeout(update, 100);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// A carousel born hidden (a closed Tab panel, a Modal, an Accordion) has
|
|
116
|
+
// no width yet: both arrows would start disabled. Watching the viewport's
|
|
117
|
+
// size updates it the moment it shows, and on every resize after that.
|
|
118
|
+
if ('ResizeObserver' in window) {
|
|
119
|
+
new ResizeObserver(update).observe(viewport);
|
|
120
|
+
} else {
|
|
121
|
+
window.addEventListener('resize', update);
|
|
122
|
+
}
|
|
123
|
+
update();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function init(root) {
|
|
127
|
+
[].slice.call((root || document).querySelectorAll('.al-carousel')).forEach(bind);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
window.alCarousel = { init: init };
|
|
131
|
+
|
|
132
|
+
if (document.readyState === 'loading') {
|
|
133
|
+
document.addEventListener('DOMContentLoaded', function () { init(); });
|
|
134
|
+
} else {
|
|
135
|
+
init();
|
|
136
|
+
}
|
|
137
|
+
})();
|
package/dist/foundation.css
CHANGED
package/dist/tokens.json
CHANGED