@sovereignfs/ui 0.21.3 → 0.33.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/Button.module.css +75 -8
- package/dist/Calendar.module.css +126 -0
- package/dist/Card.module.css +4 -2
- package/dist/Checkbox.module.css +109 -0
- package/dist/CodeTextarea.module.css +53 -0
- package/dist/ConfirmDialog.module.css +109 -0
- package/dist/DatePicker.module.css +57 -0
- package/dist/Dialog.module.css +73 -46
- package/dist/DragHandleRow.module.css +57 -0
- package/dist/Drawer.module.css +61 -0
- package/dist/FormField.module.css +5 -0
- package/dist/Icon.module.css +5 -0
- package/dist/Input.module.css +10 -0
- package/dist/Menu.module.css +82 -0
- package/dist/NavTabs.module.css +4 -2
- package/dist/OverlayHeader.module.css +98 -0
- package/dist/Popover.module.css +31 -1
- package/dist/SegmentedControl.module.css +4 -2
- package/dist/Select.module.css +9 -0
- package/dist/Sheet.module.css +55 -0
- package/dist/SplitPane.module.css +85 -0
- package/dist/StatusBadge.module.css +59 -0
- package/dist/SystemBanner.module.css +4 -2
- package/dist/Tabs.module.css +4 -2
- package/dist/TagInput.module.css +141 -0
- package/dist/Textarea.module.css +39 -0
- package/dist/Toast.module.css +7 -5
- package/dist/Toggle.module.css +1 -1
- package/dist/Tooltip.module.css +6 -1
- package/dist/index.d.ts +549 -20
- package/dist/index.js +1496 -193
- package/dist/tokens/primitives.css +19 -0
- package/dist/tokens/semantic.css +19 -0
- package/package.json +1 -1
- package/src/__tests__/motion.test.tsx +105 -0
- package/src/components/Button/Button.module.css +75 -8
- package/src/components/Button/Button.stories.tsx +6 -0
- package/src/components/Button/Button.tsx +17 -1
- package/src/components/Button/__tests__/Button.test.tsx +19 -0
- package/src/components/Calendar/Calendar.module.css +126 -0
- package/src/components/Calendar/Calendar.tsx +213 -0
- package/src/components/Calendar/__tests__/Calendar.test.tsx +98 -0
- package/src/components/Calendar/dateUtils.ts +97 -0
- package/src/components/Card/Card.module.css +4 -2
- package/src/components/Checkbox/Checkbox.module.css +109 -0
- package/src/components/Checkbox/Checkbox.tsx +78 -0
- package/src/components/CodeTextarea/CodeTextarea.module.css +53 -0
- package/src/components/CodeTextarea/CodeTextarea.stories.tsx +77 -0
- package/src/components/CodeTextarea/CodeTextarea.tsx +36 -0
- package/src/components/ConfirmDialog/ConfirmDialog.module.css +109 -0
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +124 -0
- package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +146 -0
- package/src/components/DatePicker/DatePicker.module.css +57 -0
- package/src/components/DatePicker/DatePicker.tsx +103 -0
- package/src/components/DatePicker/__tests__/DatePicker.test.tsx +89 -0
- package/src/components/Dialog/Dialog.module.css +73 -46
- package/src/components/Dialog/Dialog.stories.tsx +51 -1
- package/src/components/Dialog/Dialog.tsx +86 -22
- package/src/components/Dialog/__tests__/Dialog.test.tsx +67 -3
- package/src/components/DragHandleRow/DragHandleRow.module.css +57 -0
- package/src/components/DragHandleRow/DragHandleRow.tsx +61 -0
- package/src/components/Drawer/Drawer.module.css +61 -0
- package/src/components/Drawer/Drawer.tsx +117 -13
- package/src/components/Drawer/__tests__/Drawer.test.tsx +123 -2
- package/src/components/FormField/FormField.module.css +5 -0
- package/src/components/FormField/FormField.tsx +36 -11
- package/src/components/FormField/__tests__/FormField.test.tsx +83 -0
- package/src/components/Icon/Icon.module.css +5 -0
- package/src/components/Icon/Icon.stories.tsx +4 -3
- package/src/components/Icon/Icon.tsx +1 -1
- package/src/components/Icon/icons/calendar.tsx +23 -0
- package/src/components/Icon/icons/ellipsis-vertical.tsx +22 -0
- package/src/components/Icon/icons/index.ts +8 -4
- package/src/components/Icon/icons/sliders-horizontal.tsx +9 -9
- package/src/components/Input/Input.module.css +10 -0
- package/src/components/Menu/Menu.module.css +82 -0
- package/src/components/Menu/Menu.tsx +137 -0
- package/src/components/Menu/__tests__/Menu.test.tsx +197 -0
- package/src/components/NavTabs/NavTabs.module.css +4 -2
- package/src/components/OverlayHeader/OverlayHeader.module.css +98 -0
- package/src/components/OverlayHeader/OverlayHeader.tsx +62 -0
- package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +51 -0
- package/src/components/Popover/Popover.module.css +31 -1
- package/src/components/Popover/Popover.stories.tsx +126 -1
- package/src/components/Popover/Popover.tsx +140 -7
- package/src/components/SegmentedControl/SegmentedControl.module.css +4 -2
- package/src/components/Select/Select.module.css +9 -0
- package/src/components/Select/Select.tsx +10 -2
- package/src/components/Sheet/Sheet.module.css +55 -0
- package/src/components/Sheet/Sheet.tsx +151 -0
- package/src/components/Sheet/__tests__/Sheet.test.tsx +99 -0
- package/src/components/SplitPane/SplitPane.module.css +85 -0
- package/src/components/SplitPane/SplitPane.stories.tsx +160 -0
- package/src/components/SplitPane/SplitPane.tsx +149 -0
- package/src/components/SplitPane/__tests__/SplitPane.test.tsx +51 -0
- package/src/components/StatusBadge/StatusBadge.module.css +59 -0
- package/src/components/StatusBadge/StatusBadge.stories.tsx +74 -0
- package/src/components/StatusBadge/StatusBadge.tsx +65 -0
- package/src/components/SystemBanner/SystemBanner.module.css +4 -2
- package/src/components/Tabs/Tabs.module.css +4 -2
- package/src/components/TagInput/TagInput.module.css +141 -0
- package/src/components/TagInput/TagInput.stories.tsx +109 -0
- package/src/components/TagInput/TagInput.tsx +174 -0
- package/src/components/TagInput/__tests__/TagInput.test.tsx +69 -0
- package/src/components/Textarea/Textarea.module.css +39 -0
- package/src/components/Textarea/Textarea.tsx +15 -0
- package/src/components/Textarea/__tests__/Textarea.test.tsx +29 -0
- package/src/components/Toast/Toast.module.css +7 -5
- package/src/components/Toggle/Toggle.module.css +1 -1
- package/src/components/Tooltip/Tooltip.module.css +6 -1
- package/src/hooks/__tests__/useDoubleTap.test.tsx +117 -0
- package/src/hooks/__tests__/useIsMobile.test.tsx +80 -0
- package/src/hooks/__tests__/useLongPress.test.tsx +158 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useDoubleTap.ts +91 -0
- package/src/hooks/useIsMobile.ts +33 -0
- package/src/hooks/useLongPress.ts +185 -0
- package/src/index.ts +37 -2
- package/src/motion.ts +78 -0
- package/src/stories/Checkbox.stories.tsx +45 -0
- package/src/stories/DesignSystemOverview.stories.tsx +354 -8
- package/src/stories/DragHandleRow.stories.tsx +41 -0
- package/src/stories/FormField.stories.tsx +8 -8
- package/src/stories/InteractionHooks.stories.tsx +388 -0
- package/src/stories/MobilePatterns.stories.tsx +4 -4
- package/src/stories/Textarea.stories.tsx +51 -0
- package/src/stories/TokenGallery.stories.tsx +87 -1
- package/src/tokens/primitives.css +19 -0
- package/src/tokens/semantic.css +19 -0
- package/src/components/Icon/icons/terminal.tsx +0 -19
package/dist/Dialog.module.css
CHANGED
|
@@ -31,6 +31,16 @@
|
|
|
31
31
|
padding: var(--sv-space-8);
|
|
32
32
|
padding-left: calc(var(--sv-dialog-inset-left, 0px) + var(--sv-space-8));
|
|
33
33
|
background: var(--sv-color-scrim);
|
|
34
|
+
/* Entrance/exit fade. The Dialog component drives this via a two-phase
|
|
35
|
+
mount (see motion.ts) — .scrimOpen is added one frame after mount so this
|
|
36
|
+
transition has somewhere to animate from, and removed (while staying
|
|
37
|
+
mounted) for the exit before the component actually unmounts. */
|
|
38
|
+
opacity: 0;
|
|
39
|
+
transition: opacity var(--sv-motion-duration-base) var(--sv-motion-ease-out);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.scrimOpen {
|
|
43
|
+
opacity: 1;
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
.panel {
|
|
@@ -43,6 +53,30 @@
|
|
|
43
53
|
border: 1px solid var(--sv-color-border);
|
|
44
54
|
border-radius: var(--sv-radius-lg);
|
|
45
55
|
box-shadow: var(--sv-shadow-overlay);
|
|
56
|
+
/* Desktop entrance: fade + gentle scale-up. Same two-phase-mount pattern as
|
|
57
|
+
.scrim above. Mobile overrides this to a slide-up instead (below). */
|
|
58
|
+
opacity: 0;
|
|
59
|
+
transform: scale(0.96);
|
|
60
|
+
transition:
|
|
61
|
+
opacity var(--sv-motion-duration-base) var(--sv-motion-ease-out),
|
|
62
|
+
transform var(--sv-motion-duration-base) var(--sv-motion-ease-out);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.panelOpen {
|
|
66
|
+
opacity: 1;
|
|
67
|
+
transform: scale(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* prefers-reduced-motion: collapse both fade/scale and slide to near-instant.
|
|
71
|
+
Not literally 0 — a 0ms transition can skip applying entirely in some
|
|
72
|
+
engines, and the JS unmount timer (Dialog.tsx's MOTION_DURATION_MS,
|
|
73
|
+
switched to ~0 via usePrefersReducedMotion) needs a transition to still be
|
|
74
|
+
"present" for the visual state to actually reach its end value in time. */
|
|
75
|
+
@media (prefers-reduced-motion: reduce) {
|
|
76
|
+
.scrim,
|
|
77
|
+
.panel {
|
|
78
|
+
transition-duration: 0.01ms;
|
|
79
|
+
}
|
|
46
80
|
}
|
|
47
81
|
|
|
48
82
|
.content {
|
|
@@ -53,7 +87,7 @@
|
|
|
53
87
|
/* Disable elastic overscroll within the dialog: `none` stops both scroll-
|
|
54
88
|
chaining to the parent and the iOS rubber-band bounce inside the panel.
|
|
55
89
|
`contain` only prevents chaining — the panel itself still bounces, which
|
|
56
|
-
reveals white panel-background between
|
|
90
|
+
reveals white panel-background between OverlayHeader and the sticky tab
|
|
57
91
|
strip on iOS PWA. A dialog is a bounded surface where bounce is a visual
|
|
58
92
|
bug, not a feature. */
|
|
59
93
|
overscroll-behavior: none;
|
|
@@ -100,9 +134,11 @@
|
|
|
100
134
|
cursor: pointer;
|
|
101
135
|
}
|
|
102
136
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
137
|
+
@media (hover: hover) {
|
|
138
|
+
.close:hover {
|
|
139
|
+
background: var(--sv-color-surface-sunken);
|
|
140
|
+
color: var(--sv-color-text-primary);
|
|
141
|
+
}
|
|
106
142
|
}
|
|
107
143
|
|
|
108
144
|
.close:focus-visible {
|
|
@@ -110,8 +146,10 @@
|
|
|
110
146
|
outline-offset: 2px;
|
|
111
147
|
}
|
|
112
148
|
|
|
113
|
-
/* Hidden on desktop; shown only on mobile where it replaces the close button.
|
|
114
|
-
|
|
149
|
+
/* Hidden on desktop; shown only on mobile where it replaces the close button.
|
|
150
|
+
* OverlayHeader owns its own background/border/padding — this wrapper class
|
|
151
|
+
* only toggles visibility per breakpoint. */
|
|
152
|
+
.mobileHeader {
|
|
115
153
|
display: none;
|
|
116
154
|
}
|
|
117
155
|
|
|
@@ -122,10 +160,14 @@
|
|
|
122
160
|
@media (max-width: 768px) {
|
|
123
161
|
/* On mobile the overlay feels like a full-page push, not a floating modal.
|
|
124
162
|
Remove the dim scrim and panel chrome so it's indistinguishable from a
|
|
125
|
-
regular page render. router.back() still closes it correctly.
|
|
163
|
+
regular page render. router.back() still closes it correctly.
|
|
164
|
+
bottom stops the scrim above the footer nav (matching Drawer) instead of
|
|
165
|
+
running the full viewport height — the panel used to extend under the
|
|
166
|
+
footer with only .content's own padding compensating for it. */
|
|
126
167
|
.scrim {
|
|
127
168
|
padding: 0;
|
|
128
169
|
background: transparent;
|
|
170
|
+
bottom: var(--sv-shell-footer-height, 0);
|
|
129
171
|
}
|
|
130
172
|
|
|
131
173
|
.panel,
|
|
@@ -143,54 +185,39 @@
|
|
|
143
185
|
background: var(--sv-color-surface);
|
|
144
186
|
}
|
|
145
187
|
|
|
146
|
-
/*
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
.mobileBar {
|
|
154
|
-
display: flex;
|
|
155
|
-
align-items: center;
|
|
156
|
-
flex-shrink: 0;
|
|
157
|
-
padding: var(--sv-space-3) var(--sv-space-4);
|
|
158
|
-
background: var(--sv-color-surface);
|
|
159
|
-
border-bottom: 1px solid var(--sv-color-border);
|
|
188
|
+
/* Mobile entrance is a slide-up, not the desktop fade+scale — matches "page
|
|
189
|
+
push" framing above. No opacity change (the panel is opaque full-screen
|
|
190
|
+
content, not a floating box), so only transform needs a transition here. */
|
|
191
|
+
.panel {
|
|
192
|
+
opacity: 1;
|
|
193
|
+
transform: translateY(100%);
|
|
194
|
+
transition: transform var(--sv-motion-duration-base) var(--sv-motion-ease-out);
|
|
160
195
|
}
|
|
161
196
|
|
|
162
|
-
.
|
|
163
|
-
|
|
164
|
-
font-size: var(--sv-font-size-xl);
|
|
165
|
-
font-weight: var(--sv-font-weight-semibold);
|
|
166
|
-
color: var(--sv-color-text-primary);
|
|
197
|
+
.panelOpen {
|
|
198
|
+
transform: translateY(0);
|
|
167
199
|
}
|
|
168
200
|
|
|
169
|
-
.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
justify-content: center;
|
|
173
|
-
width: var(--sv-space-8);
|
|
174
|
-
height: var(--sv-space-8);
|
|
175
|
-
flex-shrink: 0;
|
|
176
|
-
border: none;
|
|
177
|
-
border-radius: var(--sv-radius-md);
|
|
178
|
-
background: transparent;
|
|
179
|
-
color: var(--sv-color-text-muted);
|
|
180
|
-
font-size: var(--sv-font-size-xl);
|
|
181
|
-
line-height: 1;
|
|
182
|
-
cursor: pointer;
|
|
201
|
+
/* Desktop close button is replaced by OverlayHeader on mobile. */
|
|
202
|
+
.close {
|
|
203
|
+
display: none;
|
|
183
204
|
}
|
|
184
205
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
206
|
+
/* Title + close button in one solid row, via the shared OverlayHeader
|
|
207
|
+
component. Being a flex sibling before .content means it never scrolls —
|
|
208
|
+
the content area scrolls below it. */
|
|
209
|
+
.mobileHeader {
|
|
210
|
+
display: block;
|
|
188
211
|
}
|
|
189
212
|
|
|
190
|
-
/* Remove top padding so the sticky plugin header sits flush against
|
|
191
|
-
|
|
213
|
+
/* Remove top padding so the sticky plugin header sits flush against
|
|
214
|
+
OverlayHeader — no gap for scrolling content to bleed through.
|
|
215
|
+
padding-bottom no longer adds --sv-shell-footer-height on top of
|
|
216
|
+
--sv-space-6 — the scrim above already stops short of the footer, so
|
|
217
|
+
.content doesn't need to additionally compensate for it here (that used
|
|
218
|
+
to double-count the footer clearance). */
|
|
192
219
|
.content {
|
|
193
220
|
padding-top: 0;
|
|
194
|
-
padding-bottom:
|
|
221
|
+
padding-bottom: var(--sv-space-6, 24px);
|
|
195
222
|
}
|
|
196
223
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.row {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: flex-start;
|
|
4
|
+
gap: var(--sv-space-1);
|
|
5
|
+
position: relative;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Hover-reveal only where hover genuinely exists — on touch the handle would
|
|
9
|
+
otherwise never become visible (tap-generated hover doesn't count as a
|
|
10
|
+
sustained hover state, and :not(:hover) is always true with no hover
|
|
11
|
+
capability at all), so it stays visible by default there. */
|
|
12
|
+
@media (hover: hover) {
|
|
13
|
+
.row:not(:hover) .handle,
|
|
14
|
+
.row:not(:focus-within) .handle {
|
|
15
|
+
opacity: 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.handle {
|
|
20
|
+
flex-shrink: 0;
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
width: 24px;
|
|
25
|
+
height: 24px;
|
|
26
|
+
margin-top: 2px;
|
|
27
|
+
border: none;
|
|
28
|
+
background: none;
|
|
29
|
+
padding: 0;
|
|
30
|
+
border-radius: var(--sv-radius-sm);
|
|
31
|
+
color: var(--sv-color-text-muted);
|
|
32
|
+
cursor: grab;
|
|
33
|
+
opacity: 1;
|
|
34
|
+
transition: opacity 0.1s ease;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.handle:focus-visible {
|
|
38
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
39
|
+
outline-offset: 2px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.handle:active {
|
|
43
|
+
cursor: grabbing;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.icon {
|
|
47
|
+
pointer-events: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.content {
|
|
51
|
+
flex: 1;
|
|
52
|
+
min-width: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.dragging {
|
|
56
|
+
opacity: 0.5;
|
|
57
|
+
}
|
package/dist/Drawer.module.css
CHANGED
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
display: flex;
|
|
15
15
|
align-items: flex-end;
|
|
16
16
|
background: var(--sv-color-scrim);
|
|
17
|
+
/* Entrance/exit fade — same two-phase-mount pattern as Dialog (see motion.ts
|
|
18
|
+
and Drawer.tsx). */
|
|
19
|
+
opacity: 0;
|
|
20
|
+
transition: opacity var(--sv-motion-duration-base) var(--sv-motion-ease-out);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.scrimOpen {
|
|
24
|
+
opacity: 1;
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
.panel {
|
|
@@ -28,4 +36,57 @@
|
|
|
28
36
|
/* Pull content above the home indicator / bottom bar in standalone mode. */
|
|
29
37
|
padding-bottom: env(safe-area-inset-bottom);
|
|
30
38
|
outline: none;
|
|
39
|
+
/* Slide up from below the viewport. Always a bottom sheet, so unlike Dialog
|
|
40
|
+
there's no desktop-vs-mobile fork here. */
|
|
41
|
+
transform: translateY(100%);
|
|
42
|
+
transition: transform var(--sv-motion-duration-base) var(--sv-motion-ease-out);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.panelOpen {
|
|
46
|
+
transform: translateY(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* 'half' snapHeight — a fixed height instead of the default content-sized cap
|
|
50
|
+
* above. Per the design-system's "half screen or less" convention for
|
|
51
|
+
* Drawer; content taller than this belongs in Sheet instead. */
|
|
52
|
+
.panelHalf {
|
|
53
|
+
height: 50dvh;
|
|
54
|
+
max-height: 50dvh;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* See Dialog.module.css's identical rule for why 0.01ms rather than 0. */
|
|
58
|
+
@media (prefers-reduced-motion: reduce) {
|
|
59
|
+
.scrim,
|
|
60
|
+
.panel {
|
|
61
|
+
transition-duration: 0.01ms;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Grab handle — the sole drag-initiation region for swipe-down-to-dismiss
|
|
66
|
+
* (see Drawer.tsx's doc comment for why not the whole panel). The visual bar
|
|
67
|
+
* (::before) stays the small, established 36x4px size; only the hit area
|
|
68
|
+
* grows to a comfortable touch target. */
|
|
69
|
+
.handle {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
width: 100%;
|
|
74
|
+
height: var(--sv-touch-target-min, 44px);
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
cursor: grab;
|
|
77
|
+
/* JS-driven drag — the browser must not treat a press here as a
|
|
78
|
+
scroll/pan gesture that would fight it. */
|
|
79
|
+
touch-action: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.handle:active {
|
|
83
|
+
cursor: grabbing;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.handle::before {
|
|
87
|
+
content: '';
|
|
88
|
+
width: 36px;
|
|
89
|
+
height: 4px;
|
|
90
|
+
border-radius: var(--sv-radius-full);
|
|
91
|
+
background: var(--sv-color-border-strong);
|
|
31
92
|
}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
display: flex;
|
|
3
3
|
flex-direction: column;
|
|
4
4
|
gap: var(--sv-space-1);
|
|
5
|
+
/* Every local .field/.fieldGroup class this component replaced set this
|
|
6
|
+
explicitly. Without it, a parent with align-items: flex-start (needed so
|
|
7
|
+
submit buttons don't stretch full-width) shrink-wraps this wrapper to its
|
|
8
|
+
content's intrinsic width instead of filling the available space. */
|
|
9
|
+
width: 100%;
|
|
5
10
|
}
|
|
6
11
|
|
|
7
12
|
.label {
|
package/dist/Icon.module.css
CHANGED
package/dist/Input.module.css
CHANGED
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
box-shadow 0.15s ease;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/* iOS Safari (tab and standalone PWA) auto-zooms the page whenever a focused
|
|
18
|
+
input's font-size is below 16px. The viewport intentionally allows pinch-zoom
|
|
19
|
+
(no maximum-scale — an accessibility requirement), so the only way to stop the
|
|
20
|
+
focus-zoom is to render the field at >=16px on touch devices. */
|
|
21
|
+
@media (pointer: coarse) {
|
|
22
|
+
.input {
|
|
23
|
+
font-size: var(--sv-font-size-md);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
.input::placeholder {
|
|
18
28
|
color: var(--sv-color-text-muted);
|
|
19
29
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* Menu — the item list shared by both presentations (Popover panel on
|
|
2
|
+
* desktop, Drawer panel on mobile); the presentational chrome around it is
|
|
3
|
+
* entirely owned by those two components, not duplicated here. */
|
|
4
|
+
|
|
5
|
+
.list {
|
|
6
|
+
list-style: none;
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: var(--sv-space-1);
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.item {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: var(--sv-space-2);
|
|
17
|
+
width: 100%;
|
|
18
|
+
/* Min touch target (WCAG 2.5.5 / Apple HIG) — this menu renders as a
|
|
19
|
+
Drawer sheet on mobile, so every item must be comfortably tappable. */
|
|
20
|
+
min-height: var(--sv-touch-target-min, 44px);
|
|
21
|
+
padding: var(--sv-space-2) var(--sv-space-3);
|
|
22
|
+
border: none;
|
|
23
|
+
border-radius: var(--sv-radius-sm);
|
|
24
|
+
background: transparent;
|
|
25
|
+
color: var(--sv-color-text-primary);
|
|
26
|
+
font: inherit;
|
|
27
|
+
font-size: var(--sv-font-size-sm);
|
|
28
|
+
text-align: left;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@media (hover: hover) {
|
|
33
|
+
.item:hover:not(:disabled) {
|
|
34
|
+
background: var(--sv-color-surface-sunken);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.item:focus-visible {
|
|
39
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
40
|
+
outline-offset: -2px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.item:disabled {
|
|
44
|
+
opacity: 0.5;
|
|
45
|
+
cursor: not-allowed;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.itemDestructive {
|
|
49
|
+
color: var(--sv-color-error-text);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@media (hover: hover) {
|
|
53
|
+
.itemDestructive:hover:not(:disabled) {
|
|
54
|
+
background: var(--sv-color-error-surface);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Reserved checkmark gutter for a checkable item — sized so unchecked
|
|
59
|
+
siblings in the same group still align with the checked one's icon. */
|
|
60
|
+
.check {
|
|
61
|
+
display: inline-flex;
|
|
62
|
+
flex-shrink: 0;
|
|
63
|
+
width: var(--sv-icon-size-sm);
|
|
64
|
+
height: var(--sv-icon-size-sm);
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.label {
|
|
70
|
+
padding: var(--sv-space-2) var(--sv-space-3) var(--sv-space-1);
|
|
71
|
+
font-size: var(--sv-font-size-label);
|
|
72
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
73
|
+
color: var(--sv-color-text-muted);
|
|
74
|
+
text-transform: uppercase;
|
|
75
|
+
letter-spacing: 0.04em;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.separator {
|
|
79
|
+
height: 1px;
|
|
80
|
+
margin: var(--sv-space-1) var(--sv-space-2);
|
|
81
|
+
background: var(--sv-color-border);
|
|
82
|
+
}
|
package/dist/NavTabs.module.css
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* OverlayHeader — shared fixed secondary header for Dialog's mobile mode,
|
|
2
|
+
* Sheet, and Drawer. Not itself pinned via position — the consumer renders it
|
|
3
|
+
* as a flex-shrink: 0 sibling before its own scrolling content region (see
|
|
4
|
+
* the component's own doc comment). */
|
|
5
|
+
|
|
6
|
+
.header {
|
|
7
|
+
flex-shrink: 0;
|
|
8
|
+
background: var(--sv-color-surface);
|
|
9
|
+
border-bottom: 1px solid var(--sv-color-border);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.row {
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
gap: var(--sv-space-2);
|
|
16
|
+
padding: var(--sv-space-3) var(--sv-space-4);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.backButton {
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
width: var(--sv-touch-target-min, 44px);
|
|
24
|
+
height: var(--sv-touch-target-min, 44px);
|
|
25
|
+
flex-shrink: 0;
|
|
26
|
+
/* Pull the touch-target padding back so the chevron glyph still sits close
|
|
27
|
+
to the title, matching the visual (not hit-area) spacing of .row's own
|
|
28
|
+
gap — otherwise a 44px box reads as a large empty gap before the title. */
|
|
29
|
+
margin-left: calc(-1 * (var(--sv-touch-target-min, 44px) - var(--sv-icon-size-md)) / 2);
|
|
30
|
+
margin-right: calc(
|
|
31
|
+
-1 * (var(--sv-touch-target-min, 44px) - var(--sv-icon-size-md)) / 2 + var(--sv-space-1)
|
|
32
|
+
);
|
|
33
|
+
border: none;
|
|
34
|
+
border-radius: var(--sv-radius-md);
|
|
35
|
+
background: transparent;
|
|
36
|
+
color: var(--sv-color-text-muted);
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@media (hover: hover) {
|
|
41
|
+
.backButton:hover {
|
|
42
|
+
background: var(--sv-color-surface-sunken);
|
|
43
|
+
color: var(--sv-color-text-primary);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.backButton:focus-visible {
|
|
48
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
49
|
+
outline-offset: -2px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.title {
|
|
53
|
+
flex: 1;
|
|
54
|
+
min-width: 0;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
text-overflow: ellipsis;
|
|
57
|
+
white-space: nowrap;
|
|
58
|
+
font-size: var(--sv-font-size-xl);
|
|
59
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
60
|
+
color: var(--sv-color-text-primary);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.action {
|
|
64
|
+
flex-shrink: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.closeButton {
|
|
68
|
+
display: inline-flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
width: var(--sv-touch-target-min, 44px);
|
|
72
|
+
height: var(--sv-touch-target-min, 44px);
|
|
73
|
+
flex-shrink: 0;
|
|
74
|
+
/* Pull the touch-target padding back so the × sits flush with the row's own
|
|
75
|
+
edge padding, same reasoning as .backButton above. */
|
|
76
|
+
margin-right: calc(-1 * (var(--sv-touch-target-min, 44px) - var(--sv-icon-size-sm)) / 2);
|
|
77
|
+
border: none;
|
|
78
|
+
border-radius: var(--sv-radius-md);
|
|
79
|
+
background: transparent;
|
|
80
|
+
color: var(--sv-color-text-muted);
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@media (hover: hover) {
|
|
85
|
+
.closeButton:hover {
|
|
86
|
+
background: var(--sv-color-surface-sunken);
|
|
87
|
+
color: var(--sv-color-text-primary);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.closeButton:focus-visible {
|
|
92
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
93
|
+
outline-offset: -2px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.secondRow {
|
|
97
|
+
padding: 0 var(--sv-space-2) var(--sv-space-2);
|
|
98
|
+
}
|
package/dist/Popover.module.css
CHANGED
|
@@ -11,7 +11,16 @@
|
|
|
11
11
|
border: 1px solid var(--sv-color-border);
|
|
12
12
|
border-radius: var(--sv-radius-2xl);
|
|
13
13
|
box-shadow: var(--sv-shadow-popover);
|
|
14
|
-
|
|
14
|
+
/* auto, not hidden: Popover.tsx applies an inline max-height only when the
|
|
15
|
+
panel is taller than the room actually available on whichever side it
|
|
16
|
+
opens (e.g. a tall calendar on a short mobile viewport, where neither
|
|
17
|
+
above nor below has enough space for the whole thing) — auto lets that
|
|
18
|
+
capped case scroll internally instead of silently clipping content with
|
|
19
|
+
no way to reach it. When no max-height is applied (the common case, panel
|
|
20
|
+
already fits), auto behaves identically to hidden — nothing to scroll,
|
|
21
|
+
content still clips at the rounded corners exactly as before. */
|
|
22
|
+
overflow-x: hidden;
|
|
23
|
+
overflow-y: auto;
|
|
15
24
|
/* Slide in from slightly above */
|
|
16
25
|
animation: sv-popover-in 0.12s ease;
|
|
17
26
|
}
|
|
@@ -42,3 +51,24 @@
|
|
|
42
51
|
.left {
|
|
43
52
|
left: 0;
|
|
44
53
|
}
|
|
54
|
+
|
|
55
|
+
/* Collision flip: opens above the trigger instead of below, when the panel
|
|
56
|
+
wouldn't fit within the viewport underneath it (see Popover.tsx's
|
|
57
|
+
layout-effect measurement). Same slide-in treatment, mirrored to slide up
|
|
58
|
+
from slightly below instead of down from slightly above. */
|
|
59
|
+
.upward {
|
|
60
|
+
top: auto;
|
|
61
|
+
bottom: calc(100% + var(--sv-space-2));
|
|
62
|
+
animation-name: sv-popover-in-upward;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@keyframes sv-popover-in-upward {
|
|
66
|
+
from {
|
|
67
|
+
opacity: 0;
|
|
68
|
+
transform: translateY(4px);
|
|
69
|
+
}
|
|
70
|
+
to {
|
|
71
|
+
opacity: 1;
|
|
72
|
+
transform: translateY(0);
|
|
73
|
+
}
|
|
74
|
+
}
|
package/dist/Select.module.css
CHANGED
|
@@ -33,6 +33,15 @@
|
|
|
33
33
|
border-radius: var(--sv-radius-sm);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/* iOS Safari zooms the page when a <select> under 16px receives focus, exactly
|
|
37
|
+
as it does for text inputs. Lift the default size to >=16px on touch devices;
|
|
38
|
+
the compact `.sm` variant is for dense desktop tables and is left untouched. */
|
|
39
|
+
@media (pointer: coarse) {
|
|
40
|
+
.select {
|
|
41
|
+
font-size: var(--sv-font-size-md);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
.select:focus-visible {
|
|
37
46
|
outline: none;
|
|
38
47
|
border-color: var(--sv-color-accent);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* Sheet — fills the same rect as a plugin's content area (between the shell's
|
|
2
|
+
* sticky header and footer nav — see --sv-shell-header-height /
|
|
3
|
+
* --sv-shell-footer-height, set by the shell), sliding in from an edge
|
|
4
|
+
* instead of Dialog's centered box or Drawer's partial-height sheet-over-
|
|
5
|
+
* scrim. z-index matches Dialog/Drawer's scrim so it layers consistently
|
|
6
|
+
* with any other overlay. No scrim of its own — see the component doc
|
|
7
|
+
* comment for why. */
|
|
8
|
+
|
|
9
|
+
.panel {
|
|
10
|
+
position: fixed;
|
|
11
|
+
left: 0;
|
|
12
|
+
right: 0;
|
|
13
|
+
top: var(--sv-shell-header-height, 0);
|
|
14
|
+
bottom: var(--sv-shell-footer-height, 0);
|
|
15
|
+
z-index: 100;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
background: var(--sv-color-surface);
|
|
19
|
+
outline: none;
|
|
20
|
+
transform: translateY(100%);
|
|
21
|
+
transition: transform var(--sv-motion-duration-base) var(--sv-motion-ease-out);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.panelOpen {
|
|
25
|
+
transform: translateY(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Opt-in top variant (slideFrom="top") — a short options/actions menu opened
|
|
29
|
+
* from a header button reads more naturally sliding down from that button's
|
|
30
|
+
* edge than traveling up from off-screen bottom. :not(.panelOpen) is what
|
|
31
|
+
* lets this coexist with .panelOpen above despite equal-looking single-class
|
|
32
|
+
* specificity elsewhere in this file: its (0,2,0) beats .panelOpen's (0,1,0)
|
|
33
|
+
* while closed/entering/closing, and yields to it (transform: translateY(0),
|
|
34
|
+
* same value either direction) once open. */
|
|
35
|
+
.panelFromTop:not(.panelOpen) {
|
|
36
|
+
transform: translateY(-100%);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* See Dialog.module.css's identical rule for why 0.01ms rather than 0. */
|
|
40
|
+
@media (prefers-reduced-motion: reduce) {
|
|
41
|
+
.panel {
|
|
42
|
+
transition-duration: 0.01ms;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Always the scroll container, with or without OverlayHeader present (see
|
|
47
|
+
* Sheet.tsx). No opinionated padding here — matches Drawer's convention of
|
|
48
|
+
* leaving horizontal/top padding and safe-area-bottom clearance to the
|
|
49
|
+
* content itself, since a Sheet's content shape varies far more than a
|
|
50
|
+
* fixed-size Dialog's. */
|
|
51
|
+
.content {
|
|
52
|
+
flex: 1 1 auto;
|
|
53
|
+
overflow-y: auto;
|
|
54
|
+
overscroll-behavior: contain;
|
|
55
|
+
}
|