@sovereignfs/ui 0.45.0 → 0.54.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/dist/LegalLinks.module.css +24 -0
- package/dist/Markdown.module.css +49 -0
- package/dist/MobileAppsDrawer.module.css +81 -0
- package/dist/MobileFooter.module.css +73 -0
- package/dist/MobileHeader.module.css +63 -0
- package/dist/PageContainer.module.css +20 -0
- package/dist/SwipableMobileCarousel.module.css +55 -0
- package/dist/SwipableMobileCarouselDots.module.css +49 -0
- package/dist/SwipableMobileCarouselSlideParts.module.css +29 -0
- package/dist/index.d.ts +654 -6
- package/dist/index.js +1856 -1133
- package/dist/tokens/primitives.css +25 -0
- package/package.json +13 -9
- package/src/components/Badge/Badge.tsx +1 -7
- package/src/components/DeviceOnlyGate/DeviceOnlyGate.tsx +58 -0
- package/src/components/DeviceOnlyGate/__tests__/DeviceOnlyGate.test.tsx +45 -0
- package/src/components/DeviceStorageKeyGate/DeviceStorageKeyGate.tsx +100 -0
- package/src/components/DeviceStorageKeyGate/__tests__/DeviceStorageKeyGate.test.tsx +119 -0
- package/src/components/Dialog/Dialog.tsx +8 -59
- package/src/components/Drawer/Drawer.stories.tsx +44 -0
- package/src/components/Drawer/Drawer.tsx +18 -56
- package/src/components/Drawer/__tests__/Drawer.test.tsx +22 -0
- package/src/components/Icon/icons/index.ts +8 -0
- package/src/components/Icon/icons/menu.tsx +22 -0
- package/src/components/Icon/icons/smartphone.tsx +21 -0
- package/src/components/LegalLinks/LegalLinks.module.css +24 -0
- package/src/components/LegalLinks/LegalLinks.stories.tsx +22 -0
- package/src/components/LegalLinks/LegalLinks.tsx +40 -0
- package/src/components/Markdown/Markdown.module.css +49 -0
- package/src/components/Markdown/Markdown.stories.tsx +30 -0
- package/src/components/Markdown/Markdown.tsx +168 -0
- package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +81 -0
- package/src/components/MobileAppsDrawer/MobileAppsDrawer.tsx +78 -0
- package/src/components/MobileAppsDrawer/__tests__/MobileAppsDrawer.test.tsx +80 -0
- package/src/components/MobileFooter/MobileFooter.module.css +73 -0
- package/src/components/MobileFooter/MobileFooter.stories.tsx +47 -0
- package/src/components/MobileFooter/MobileFooter.tsx +109 -0
- package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +71 -0
- package/src/components/MobileHeader/MobileHeader.module.css +63 -0
- package/src/components/MobileHeader/MobileHeader.stories.tsx +57 -0
- package/src/components/MobileHeader/MobileHeader.tsx +77 -0
- package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +58 -0
- package/src/components/NavTabs/NavTabs.tsx +39 -11
- package/src/components/OfflineGate/OfflineGate.tsx +44 -0
- package/src/components/OfflineGate/__tests__/OfflineGate.test.tsx +66 -0
- package/src/components/PageContainer/PageContainer.module.css +20 -0
- package/src/components/PageContainer/PageContainer.tsx +31 -0
- package/src/components/PageContainer/__tests__/PageContainer.test.tsx +37 -0
- package/src/components/PageHeader/PageHeader.tsx +16 -2
- package/src/components/ResponsiveSurface/ResponsiveSurface.tsx +24 -0
- package/src/components/ResponsiveSurface/__tests__/ResponsiveSurface.test.tsx +73 -0
- package/src/components/Sheet/Sheet.tsx +9 -51
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +55 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +227 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlide.tsx +35 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody.tsx +42 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter.tsx +18 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader.tsx +21 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideParts.module.css +29 -0
- package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +133 -0
- package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarouselDevGuard.test.tsx +61 -0
- package/src/components/SwipableMobileCarousel/context.ts +13 -0
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +49 -0
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +62 -0
- package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +63 -0
- package/src/hooks/__tests__/useCarouselRouteSync.test.tsx +126 -0
- package/src/hooks/__tests__/useIsOffline.test.tsx +53 -0
- package/src/hooks/__tests__/useOfflineTileState.test.tsx +69 -0
- package/src/hooks/__tests__/useResponsiveLayout.test.tsx +87 -0
- package/src/hooks/__tests__/useSnapCarousel.test.tsx +93 -0
- package/src/hooks/__tests__/useSwipeReveal.test.tsx +110 -0
- package/src/hooks/index.ts +18 -0
- package/src/hooks/useCarouselRouteSync.ts +92 -0
- package/src/hooks/useIsOffline.ts +41 -0
- package/src/hooks/useOfflineTileState.ts +45 -0
- package/src/hooks/useResponsiveLayout.ts +36 -0
- package/src/hooks/useSnapCarousel.ts +79 -0
- package/src/hooks/useSwipeReveal.ts +107 -0
- package/src/index.ts +63 -2
- package/src/overlay-shell.ts +94 -0
- package/src/stories/DesignSystemOverview.stories.tsx +322 -1
- package/src/stories/DeviceOnlyGate.stories.tsx +35 -0
- package/src/stories/DeviceStorageKeyGate.stories.tsx +57 -0
- package/src/stories/InteractionHooks.stories.tsx +483 -0
- package/src/stories/MobilePatterns.stories.tsx +247 -2
- package/src/stories/NavTabs.stories.tsx +18 -0
- package/src/stories/OfflineGate.stories.tsx +42 -0
- package/src/stories/PageContainer.stories.tsx +37 -0
- package/src/stories/PageHeader.stories.tsx +11 -0
- package/src/stories/ResponsiveSurface.stories.tsx +47 -0
- package/src/stories/SwipableMobileCarousel.stories.tsx +304 -0
- package/src/stories/TokenGallery.stories.tsx +65 -0
- package/src/tokens/primitives.css +25 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
margin: var(--sv-space-5) 0 0;
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
gap: var(--sv-space-2);
|
|
7
|
+
font-size: var(--sv-font-size-xs);
|
|
8
|
+
color: var(--sv-color-text-muted);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.link {
|
|
12
|
+
color: var(--sv-color-text-muted);
|
|
13
|
+
text-decoration: underline;
|
|
14
|
+
text-underline-offset: 2px;
|
|
15
|
+
text-decoration-color: var(--sv-color-border);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.link:hover {
|
|
19
|
+
color: var(--sv-color-text-primary);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.dot {
|
|
23
|
+
color: var(--sv-color-border);
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* Spacing lives on this wrapper div, not on the Typography-rendered tag
|
|
2
|
+
inside it — Typography's own variants reset margin: 0, which would tie
|
|
3
|
+
for CSS specificity against a bare `.root > * + *` sibling rule and make
|
|
4
|
+
the outcome depend on module load order. A dedicated class avoids that. */
|
|
5
|
+
.block {
|
|
6
|
+
margin-top: var(--sv-space-4);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.block:first-child {
|
|
10
|
+
margin-top: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.block[data-heading] {
|
|
14
|
+
margin-top: var(--sv-space-8);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.block[data-heading]:first-child {
|
|
18
|
+
margin-top: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.blockquote {
|
|
22
|
+
margin: 0;
|
|
23
|
+
padding: var(--sv-space-4);
|
|
24
|
+
border-left: 3px solid var(--sv-color-border);
|
|
25
|
+
background: var(--sv-color-surface-sunken);
|
|
26
|
+
border-radius: var(--sv-radius-sm);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.list {
|
|
30
|
+
margin: 0;
|
|
31
|
+
padding-left: var(--sv-space-6);
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
gap: var(--sv-space-2);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.root a {
|
|
38
|
+
color: var(--sv-color-text-primary);
|
|
39
|
+
text-decoration: underline;
|
|
40
|
+
text-underline-offset: 2px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.root code {
|
|
44
|
+
font-family: var(--sv-font-family-mono);
|
|
45
|
+
font-size: 0.9em;
|
|
46
|
+
background: var(--sv-color-surface-sunken);
|
|
47
|
+
padding: 0.1em 0.35em;
|
|
48
|
+
border-radius: var(--sv-radius-sm);
|
|
49
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* Matches the runtime shell's own Apps drawer grid exactly
|
|
2
|
+
(runtime/app/(platform)/_components/MobileNav.module.css's .drawerGrid/
|
|
3
|
+
.drawerGridItem/.drawerGridIcon/.drawerGridName) — this component is
|
|
4
|
+
meant to replace that hand-rolled markup for any consumer building its
|
|
5
|
+
own Apps-style launcher. No header row — see the component's own doc
|
|
6
|
+
comment for why. */
|
|
7
|
+
|
|
8
|
+
.grid {
|
|
9
|
+
list-style: none;
|
|
10
|
+
margin: 0;
|
|
11
|
+
/* Drawer's own grab handle already reserves a 44px touch target above this
|
|
12
|
+
— no extra top padding needed beyond a touch of breathing room. Bottom
|
|
13
|
+
gets noticeably more: with no header row to separate the grid from the
|
|
14
|
+
handle, a tight top/bottom balance read as top-heavy (grid crowding the
|
|
15
|
+
handle isn't the issue; the *last* row crowding the panel's bottom
|
|
16
|
+
edge/safe-area is). Drawer's own panel already adds
|
|
17
|
+
env(safe-area-inset-bottom) below everything, so this is on top of that,
|
|
18
|
+
not instead of it. */
|
|
19
|
+
padding: var(--sv-space-1) var(--sv-space-4) var(--sv-space-10);
|
|
20
|
+
display: grid;
|
|
21
|
+
grid-template-columns: repeat(3, 1fr);
|
|
22
|
+
row-gap: var(--sv-space-5);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.gridItem {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: var(--sv-space-2);
|
|
30
|
+
padding: var(--sv-space-1);
|
|
31
|
+
border: none;
|
|
32
|
+
background: none;
|
|
33
|
+
text-decoration: none;
|
|
34
|
+
color: var(--sv-color-text-primary);
|
|
35
|
+
font: inherit;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
width: 100%;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@media (hover: hover) {
|
|
41
|
+
.gridItem:hover .gridIcon {
|
|
42
|
+
background: var(--sv-color-border);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.gridItem:focus-visible {
|
|
47
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
48
|
+
outline-offset: 2px;
|
|
49
|
+
border-radius: var(--sv-radius-md);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* color: text-primary, not text-muted — the reference's own drawerGridIcon
|
|
53
|
+
uses text-muted only as its monogram-fallback text color for <img>-less
|
|
54
|
+
plugin icons (raster <img> icons don't inherit CSS color at all, so that
|
|
55
|
+
value never actually painted a real icon). Every tile here renders a real
|
|
56
|
+
stroke icon via the Icon component (currentColor), which does inherit —
|
|
57
|
+
matching the reference's own explicit override for that exact case
|
|
58
|
+
(.drawerGridIconSettings, used by its Console tile) rather than the
|
|
59
|
+
fallback default. */
|
|
60
|
+
.gridIcon {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
width: 56px;
|
|
65
|
+
height: 56px;
|
|
66
|
+
border-radius: 14px;
|
|
67
|
+
background: var(--sv-color-surface-sunken);
|
|
68
|
+
font-size: var(--sv-font-size-sm);
|
|
69
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
70
|
+
color: var(--sv-color-text-primary);
|
|
71
|
+
transition: background 0.1s;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.gridName {
|
|
75
|
+
font-size: var(--sv-font-size-sm);
|
|
76
|
+
text-align: center;
|
|
77
|
+
color: var(--sv-color-text-primary);
|
|
78
|
+
line-height: 1.3;
|
|
79
|
+
word-break: break-word;
|
|
80
|
+
max-width: 80px;
|
|
81
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
.footer {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
width: 100%;
|
|
5
|
+
/* Mobile-only component — never render wider than the platform's own
|
|
6
|
+
mobile breakpoint, even if a consumer accidentally mounts it in a wider
|
|
7
|
+
container (e.g. an un-gated preview). At real mobile widths this is a
|
|
8
|
+
no-op since the viewport itself is already narrower. */
|
|
9
|
+
max-width: 768px;
|
|
10
|
+
margin-inline: auto;
|
|
11
|
+
min-height: var(--sv-shell-footer-height, 60px);
|
|
12
|
+
padding: 0 var(--sv-space-4);
|
|
13
|
+
padding-bottom: max(var(--sv-space-2), env(safe-area-inset-bottom));
|
|
14
|
+
background-color: var(--sv-color-surface);
|
|
15
|
+
border-top: 1px solid var(--sv-color-border);
|
|
16
|
+
position: relative;
|
|
17
|
+
z-index: 101;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.navItem {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
flex: 1;
|
|
25
|
+
min-height: 52px;
|
|
26
|
+
border: none;
|
|
27
|
+
background: transparent;
|
|
28
|
+
color: var(--sv-color-text-muted);
|
|
29
|
+
font: inherit;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
text-decoration: none;
|
|
32
|
+
border-radius: var(--sv-radius-md);
|
|
33
|
+
transition: color 0.1s;
|
|
34
|
+
outline: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@media (hover: hover) {
|
|
38
|
+
.navItem:hover {
|
|
39
|
+
color: var(--sv-color-text-primary);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.navItem:focus-visible {
|
|
44
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
45
|
+
outline-offset: -2px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.navItemActive {
|
|
49
|
+
color: var(--sv-color-text-primary);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.navItemApps {
|
|
53
|
+
position: relative;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.navItemAppsOpen {
|
|
57
|
+
color: var(--sv-color-text-primary);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.navItemAppsOpen::before {
|
|
61
|
+
content: '';
|
|
62
|
+
position: absolute;
|
|
63
|
+
width: 48px;
|
|
64
|
+
height: 48px;
|
|
65
|
+
border-radius: var(--sv-radius-full);
|
|
66
|
+
background: var(--sv-color-surface-sunken);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.navItemAppsOpen svg,
|
|
70
|
+
.navItemAppsOpen img {
|
|
71
|
+
position: relative;
|
|
72
|
+
z-index: 1;
|
|
73
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
.header {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
gap: var(--sv-space-3);
|
|
6
|
+
width: 100%;
|
|
7
|
+
/* Mobile-only component — never render wider than the platform's own
|
|
8
|
+
mobile breakpoint, even if a consumer accidentally mounts it in a wider
|
|
9
|
+
container (e.g. an un-gated preview). At real mobile widths this is a
|
|
10
|
+
no-op since the viewport itself is already narrower. */
|
|
11
|
+
max-width: 768px;
|
|
12
|
+
margin-inline: auto;
|
|
13
|
+
min-height: var(--sv-shell-header-height, 60px);
|
|
14
|
+
padding: var(--sv-space-3) var(--sv-space-4);
|
|
15
|
+
padding-top: max(var(--sv-space-3), env(safe-area-inset-top));
|
|
16
|
+
padding-right: max(var(--sv-space-4), env(safe-area-inset-right));
|
|
17
|
+
padding-left: max(var(--sv-space-4), env(safe-area-inset-left));
|
|
18
|
+
background-color: var(--sv-color-surface);
|
|
19
|
+
border-bottom: 1px solid var(--sv-color-border);
|
|
20
|
+
position: sticky;
|
|
21
|
+
top: 0;
|
|
22
|
+
z-index: 101;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.brandGroup {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: var(--sv-space-3);
|
|
29
|
+
min-width: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Matches the runtime shell's own .mobileBrandIcon (shell.module.css) — the
|
|
33
|
+
default logo shown when the consumer doesn't supply one. */
|
|
34
|
+
.defaultLogo {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
flex-shrink: 0;
|
|
39
|
+
width: 36px;
|
|
40
|
+
height: 36px;
|
|
41
|
+
border-radius: var(--sv-radius-md);
|
|
42
|
+
background-color: var(--sv-color-accent);
|
|
43
|
+
color: var(--sv-color-text-on-accent);
|
|
44
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.title {
|
|
49
|
+
font-size: var(--sv-font-size-md);
|
|
50
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
51
|
+
color: var(--sv-color-text-primary);
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
text-overflow: ellipsis;
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.right {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: var(--sv-space-2);
|
|
61
|
+
margin-left: auto;
|
|
62
|
+
flex-shrink: 0;
|
|
63
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
.wrap {
|
|
2
|
+
position: relative;
|
|
3
|
+
height: 100%;
|
|
4
|
+
width: 100%;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.scroller {
|
|
8
|
+
height: 100%;
|
|
9
|
+
width: 100%;
|
|
10
|
+
display: flex;
|
|
11
|
+
overflow-x: auto;
|
|
12
|
+
overflow-y: hidden;
|
|
13
|
+
scroll-snap-type: x mandatory;
|
|
14
|
+
/* Momentum + rubber-banding come from the browser's native scroll physics —
|
|
15
|
+
no hand-rolled pointer/touch dragging. Deliberately NOT
|
|
16
|
+
-webkit-overflow-scrolling: touch: that legacy iOS <13 hint promotes its
|
|
17
|
+
element to its own compositing layer for GPU-accelerated momentum
|
|
18
|
+
scroll, and that layer's paint order relative to siblings/fixed
|
|
19
|
+
descendants elsewhere in the tree isn't always reliable on real iOS
|
|
20
|
+
Safari (confirmed via sovereign-tasks' and sovereign-shopper's own
|
|
21
|
+
carousels, which hit and fixed this exact bug — a position:fixed
|
|
22
|
+
overlay opened above the scroller painted BEHIND it instead of above).
|
|
23
|
+
Removing it doesn't disable momentum scrolling on any iOS version this
|
|
24
|
+
platform realistically targets. */
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.slide {
|
|
28
|
+
flex: 0 0 100%;
|
|
29
|
+
width: 100%;
|
|
30
|
+
height: 100%;
|
|
31
|
+
min-width: 0;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
scroll-snap-align: start;
|
|
34
|
+
scroll-snap-stop: always;
|
|
35
|
+
/* Slide content is a flex column so Header/Footer stay their natural size
|
|
36
|
+
and Body (flex: 1 1 auto, min-height: 0) fills and scrolls the rest —
|
|
37
|
+
see SwipableMobileCarouselSlideParts.module.css. */
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.dots {
|
|
43
|
+
/* Overlays the bottom of .wrap rather than taking its own layout row —
|
|
44
|
+
matches both sovereign-tasks' and sovereign-shopper's existing dots
|
|
45
|
+
placement. */
|
|
46
|
+
position: absolute;
|
|
47
|
+
left: 0;
|
|
48
|
+
right: 0;
|
|
49
|
+
bottom: var(--sv-space-3);
|
|
50
|
+
pointer-events: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.dots > * {
|
|
54
|
+
pointer-events: auto;
|
|
55
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
.dots {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
gap: var(--sv-space-2);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.dot {
|
|
9
|
+
/* A 6px visible dot is too small a hit target on its own — the button's
|
|
10
|
+
real box is larger (20px) with the dot itself painted by ::before and
|
|
11
|
+
centered inside, so tap area grows without changing the visible dot
|
|
12
|
+
size or the row's gap-based spacing math. */
|
|
13
|
+
position: relative;
|
|
14
|
+
width: 20px;
|
|
15
|
+
height: 20px;
|
|
16
|
+
padding: 0;
|
|
17
|
+
border: none;
|
|
18
|
+
background: transparent;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.dot::before {
|
|
23
|
+
content: '';
|
|
24
|
+
position: absolute;
|
|
25
|
+
top: 50%;
|
|
26
|
+
left: 50%;
|
|
27
|
+
width: 6px;
|
|
28
|
+
height: 6px;
|
|
29
|
+
border-radius: 50%;
|
|
30
|
+
background: var(--sv-color-border-strong);
|
|
31
|
+
transform: translate(-50%, -50%);
|
|
32
|
+
transition: background 0.15s ease;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.dot:focus-visible {
|
|
36
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
37
|
+
outline-offset: 2px;
|
|
38
|
+
border-radius: 50%;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.dotActive::before {
|
|
42
|
+
background: var(--sv-color-text-primary);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@media (hover: hover) {
|
|
46
|
+
.dot:not(.dotActive):hover::before {
|
|
47
|
+
background: var(--sv-color-text-muted);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.header,
|
|
2
|
+
.footer {
|
|
3
|
+
flex: 0 0 auto;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.body {
|
|
7
|
+
flex: 1 1 auto;
|
|
8
|
+
min-height: 0; /* allow shrinking inside the slide's flex column, so it can scroll instead of overflowing */
|
|
9
|
+
overflow-y: auto;
|
|
10
|
+
overflow-x: hidden;
|
|
11
|
+
/* Deliberately does NOT set touch-action: pan-y here. touch-action's
|
|
12
|
+
effective value is the INTERSECTION of this element's and every
|
|
13
|
+
ancestor's value (docs/architecture-rules.md) — the ancestor .scroller
|
|
14
|
+
(SwipableMobileCarousel.module.css) needs the horizontal swipe gesture,
|
|
15
|
+
so pan-y here ∩ that horizontal handling would resolve to neither axis
|
|
16
|
+
working natively. Relying on the browser's default touch-action (auto)
|
|
17
|
+
here lets native gesture arbitration between the two nested scrollers
|
|
18
|
+
sort out which axis wins per-gesture — don't "fix" perceived nested-
|
|
19
|
+
scroll jank by adding pan-y; it silently breaks horizontal swipe instead. */
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.loading {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
height: 100%;
|
|
27
|
+
color: var(--sv-color-text-subtle);
|
|
28
|
+
font-size: var(--sv-font-size-sm);
|
|
29
|
+
}
|