@sovereignfs/ui 0.23.0 → 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 +14 -2
- 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 +9 -3
- package/dist/Drawer.module.css +61 -0
- package/dist/Icon.module.css +5 -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/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/Toast.module.css +4 -2
- package/dist/Tooltip.module.css +6 -1
- package/dist/index.d.ts +493 -17
- package/dist/index.js +1390 -209
- package/dist/tokens/primitives.css +19 -0
- package/dist/tokens/semantic.css +13 -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 +14 -2
- package/src/components/Checkbox/Checkbox.tsx +22 -12
- 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 +9 -3
- 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/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/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/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/Toast/Toast.module.css +4 -2
- 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 +30 -1
- package/src/motion.ts +78 -0
- package/src/stories/DesignSystemOverview.stories.tsx +292 -4
- package/src/stories/InteractionHooks.stories.tsx +388 -0
- package/src/stories/TokenGallery.stories.tsx +85 -1
- package/src/tokens/primitives.css +19 -0
- package/src/tokens/semantic.css +13 -0
- package/src/components/Icon/icons/terminal.tsx +0 -19
package/dist/Button.module.css
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
font-weight: var(--sv-font-weight-medium);
|
|
10
10
|
line-height: 1;
|
|
11
11
|
cursor: pointer;
|
|
12
|
+
/* Removes the ~300ms double-tap-to-zoom delay on touch — this is a tap
|
|
13
|
+
target, never a pinch-zoom surface. */
|
|
14
|
+
touch-action: manipulation;
|
|
12
15
|
transition:
|
|
13
16
|
background-color 0.15s ease,
|
|
14
17
|
border-color 0.15s ease,
|
|
@@ -25,6 +28,15 @@
|
|
|
25
28
|
cursor: not-allowed;
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
/* Touch target: gated to (pointer: coarse) — the *primary* pointer, not
|
|
32
|
+
(any-pointer: coarse) — so a touchscreen laptop with mouse/trackpad as
|
|
33
|
+
primary keeps desktop density (see mobile design-system plan, Part 4 #5). */
|
|
34
|
+
@media (pointer: coarse) {
|
|
35
|
+
.button {
|
|
36
|
+
min-height: var(--sv-touch-target-min, 44px);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
/* Sizes */
|
|
29
41
|
.sm {
|
|
30
42
|
padding: var(--sv-space-1) var(--sv-space-3);
|
|
@@ -37,14 +49,51 @@
|
|
|
37
49
|
font-weight: var(--sv-font-weight-semibold);
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
/* Loading spinner — a self-contained token-only spinner rather than reusing
|
|
53
|
+
the Spinner component: Spinner's border colors are semantic-fixed
|
|
54
|
+
(`--sv-color-accent`), which would be invisible against a primary button's
|
|
55
|
+
accent background, and overriding a second CSS module's rule on the same
|
|
56
|
+
element is a cross-module cascade-order gamble (see ConfirmDialog's
|
|
57
|
+
`.destructiveConfirm` comment for the same tradeoff). `currentColor` picks
|
|
58
|
+
up each variant's own text color for free. */
|
|
59
|
+
.spinner {
|
|
60
|
+
display: inline-block;
|
|
61
|
+
width: var(--sv-icon-size-sm);
|
|
62
|
+
height: var(--sv-icon-size-sm);
|
|
63
|
+
flex-shrink: 0;
|
|
64
|
+
border: 2px solid currentColor;
|
|
65
|
+
border-top-color: transparent;
|
|
66
|
+
border-radius: var(--sv-radius-full);
|
|
67
|
+
opacity: 0.7;
|
|
68
|
+
animation: button-spin 0.7s linear infinite;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@keyframes button-spin {
|
|
72
|
+
to {
|
|
73
|
+
transform: rotate(360deg);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@media (prefers-reduced-motion: reduce) {
|
|
78
|
+
.spinner {
|
|
79
|
+
animation-play-state: paused;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
40
83
|
/* Variants */
|
|
41
84
|
.primary {
|
|
42
85
|
background-color: var(--sv-color-accent);
|
|
43
86
|
color: var(--sv-color-text-on-accent);
|
|
44
87
|
}
|
|
45
88
|
|
|
46
|
-
|
|
47
|
-
|
|
89
|
+
@media (hover: hover) {
|
|
90
|
+
.primary:hover:not(:disabled) {
|
|
91
|
+
background-color: var(--sv-color-accent-hover);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.primary:active:not(:disabled) {
|
|
96
|
+
background-color: color-mix(in srgb, var(--sv-color-accent) 80%, black);
|
|
48
97
|
}
|
|
49
98
|
|
|
50
99
|
.secondary {
|
|
@@ -53,8 +102,14 @@
|
|
|
53
102
|
border-color: var(--sv-color-border-strong);
|
|
54
103
|
}
|
|
55
104
|
|
|
56
|
-
|
|
57
|
-
|
|
105
|
+
@media (hover: hover) {
|
|
106
|
+
.secondary:hover:not(:disabled) {
|
|
107
|
+
background-color: var(--sv-color-surface-sunken);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.secondary:active:not(:disabled) {
|
|
112
|
+
background-color: color-mix(in srgb, var(--sv-color-surface-sunken) 85%, black);
|
|
58
113
|
}
|
|
59
114
|
|
|
60
115
|
.ghost {
|
|
@@ -62,8 +117,14 @@
|
|
|
62
117
|
color: var(--sv-color-text-primary);
|
|
63
118
|
}
|
|
64
119
|
|
|
65
|
-
|
|
66
|
-
|
|
120
|
+
@media (hover: hover) {
|
|
121
|
+
.ghost:hover:not(:disabled) {
|
|
122
|
+
background-color: var(--sv-color-surface-sunken);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.ghost:active:not(:disabled) {
|
|
127
|
+
background-color: color-mix(in srgb, var(--sv-color-surface-sunken) 85%, black);
|
|
67
128
|
}
|
|
68
129
|
|
|
69
130
|
.destructive {
|
|
@@ -72,6 +133,12 @@
|
|
|
72
133
|
border-color: var(--sv-color-error-border);
|
|
73
134
|
}
|
|
74
135
|
|
|
75
|
-
|
|
76
|
-
|
|
136
|
+
@media (hover: hover) {
|
|
137
|
+
.destructive:hover:not(:disabled) {
|
|
138
|
+
background-color: var(--sv-color-error-surface);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.destructive:active:not(:disabled) {
|
|
143
|
+
background-color: color-mix(in srgb, var(--sv-color-error-surface) 85%, black);
|
|
77
144
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* Calendar — month grid. Tokens only. Grid columns are equal-width via
|
|
2
|
+
* `1fr` repeated 7 times rather than a fixed px size, so the component
|
|
3
|
+
* scales cleanly inside both a Popover panel (desktop) and a full-width
|
|
4
|
+
* Drawer sheet (mobile). */
|
|
5
|
+
|
|
6
|
+
.calendar {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: var(--sv-space-3);
|
|
10
|
+
font-family: var(--sv-font-family);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.header {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
gap: var(--sv-space-2);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.navButton {
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
width: var(--sv-touch-target-min, 44px);
|
|
25
|
+
height: var(--sv-touch-target-min, 44px);
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
border: none;
|
|
28
|
+
border-radius: var(--sv-radius-md);
|
|
29
|
+
background: transparent;
|
|
30
|
+
color: var(--sv-color-text-muted);
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@media (hover: hover) {
|
|
35
|
+
.navButton:hover {
|
|
36
|
+
background: var(--sv-color-surface-sunken);
|
|
37
|
+
color: var(--sv-color-text-primary);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.navButton:focus-visible {
|
|
42
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
43
|
+
outline-offset: -2px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.monthLabel {
|
|
47
|
+
font-size: var(--sv-font-size-sm);
|
|
48
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
49
|
+
color: var(--sv-color-text-primary);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.grid {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
gap: var(--sv-space-1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.weekdayRow,
|
|
59
|
+
.week {
|
|
60
|
+
display: grid;
|
|
61
|
+
grid-template-columns: repeat(7, 1fr);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.weekday {
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
height: var(--sv-space-6);
|
|
69
|
+
font-size: var(--sv-font-size-xs);
|
|
70
|
+
font-weight: var(--sv-font-weight-medium);
|
|
71
|
+
color: var(--sv-color-text-subtle);
|
|
72
|
+
text-transform: uppercase;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.day {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
justify-content: center;
|
|
79
|
+
width: 100%;
|
|
80
|
+
/* Square-ish cell that also clears the touch-target minimum — height
|
|
81
|
+
drives the grid's row size; width fills its 1fr column. */
|
|
82
|
+
min-height: var(--sv-touch-target-min, 44px);
|
|
83
|
+
border: none;
|
|
84
|
+
border-radius: var(--sv-radius-md);
|
|
85
|
+
background: transparent;
|
|
86
|
+
color: var(--sv-color-text-primary);
|
|
87
|
+
font-size: var(--sv-font-size-sm);
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@media (hover: hover) {
|
|
92
|
+
.day:hover:not(:disabled) {
|
|
93
|
+
background: var(--sv-color-surface-sunken);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.day:focus-visible {
|
|
98
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
99
|
+
outline-offset: -2px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.day:disabled {
|
|
103
|
+
color: var(--sv-color-text-subtle);
|
|
104
|
+
cursor: not-allowed;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.dayOutside {
|
|
108
|
+
color: var(--sv-color-text-subtle);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.dayToday {
|
|
112
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
113
|
+
box-shadow: inset 0 0 0 1px var(--sv-color-border-strong);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.daySelected,
|
|
117
|
+
.daySelected:disabled {
|
|
118
|
+
background: var(--sv-color-accent);
|
|
119
|
+
color: var(--sv-color-text-on-accent);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@media (hover: hover) {
|
|
123
|
+
.daySelected:hover:not(:disabled) {
|
|
124
|
+
background: var(--sv-color-accent-hover);
|
|
125
|
+
}
|
|
126
|
+
}
|
package/dist/Card.module.css
CHANGED
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
transition: border-color 0.15s ease;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
@media (hover: hover) {
|
|
28
|
+
.interactive:hover {
|
|
29
|
+
border-color: var(--sv-color-border-strong);
|
|
30
|
+
}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
.interactive:focus-visible {
|
package/dist/Checkbox.module.css
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
/* The visual box */
|
|
9
9
|
.box {
|
|
10
|
+
--checkbox-box-size: 18px;
|
|
10
11
|
position: relative;
|
|
11
12
|
flex-shrink: 0;
|
|
12
|
-
width:
|
|
13
|
-
height:
|
|
13
|
+
width: var(--checkbox-box-size);
|
|
14
|
+
height: var(--checkbox-box-size);
|
|
14
15
|
border-radius: var(--sv-radius-sm);
|
|
15
16
|
border: 1.5px solid var(--sv-color-border-strong);
|
|
16
17
|
background: var(--sv-color-surface);
|
|
@@ -43,6 +44,17 @@
|
|
|
43
44
|
cursor: not-allowed;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/* Expand the tappable region to the platform's minimum touch target without
|
|
48
|
+
growing the visible box — consumers (e.g. task rows) align the box to the
|
|
49
|
+
pixel against header icons, so its visual footprint and layout must stay
|
|
50
|
+
identical. Gated to (pointer: coarse), the *primary* pointer, so a
|
|
51
|
+
touchscreen laptop with mouse/trackpad as primary keeps desktop density. */
|
|
52
|
+
@media (pointer: coarse) {
|
|
53
|
+
.input {
|
|
54
|
+
inset: calc((var(--checkbox-box-size) - var(--sv-touch-target-min, 44px)) / 2);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
.input:focus-visible + .tick,
|
|
47
59
|
.input:focus-visible {
|
|
48
60
|
outline: none;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.textarea {
|
|
2
|
+
display: block;
|
|
3
|
+
width: 100%;
|
|
4
|
+
min-height: 180px;
|
|
5
|
+
max-height: min(70vh, 720px);
|
|
6
|
+
padding: var(--sv-space-3);
|
|
7
|
+
font-family: var(--sv-font-family-mono);
|
|
8
|
+
font-size: var(--sv-font-size-sm);
|
|
9
|
+
line-height: 1.6;
|
|
10
|
+
color: var(--sv-color-text-primary);
|
|
11
|
+
tab-size: 2;
|
|
12
|
+
white-space: pre;
|
|
13
|
+
overflow: auto;
|
|
14
|
+
resize: vertical;
|
|
15
|
+
background: var(--sv-color-surface);
|
|
16
|
+
border: 1px solid var(--sv-color-border-strong);
|
|
17
|
+
border-radius: var(--sv-radius-md);
|
|
18
|
+
transition:
|
|
19
|
+
border-color 0.15s ease,
|
|
20
|
+
box-shadow 0.15s ease;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.textarea::placeholder {
|
|
24
|
+
color: var(--sv-color-text-muted);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.textarea:focus-visible {
|
|
28
|
+
outline: none;
|
|
29
|
+
border-color: var(--sv-color-accent);
|
|
30
|
+
box-shadow: 0 0 0 2px var(--sv-color-focus-ring);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.textarea:read-only {
|
|
34
|
+
color: var(--sv-color-text-muted);
|
|
35
|
+
background: var(--sv-color-surface-sunken);
|
|
36
|
+
border-color: var(--sv-color-border);
|
|
37
|
+
cursor: default;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.textarea:disabled {
|
|
41
|
+
opacity: 0.5;
|
|
42
|
+
cursor: not-allowed;
|
|
43
|
+
resize: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.invalid {
|
|
47
|
+
border-color: var(--sv-color-error-border);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.invalid:focus-visible {
|
|
51
|
+
border-color: var(--sv-color-error-border);
|
|
52
|
+
box-shadow: 0 0 0 2px var(--sv-color-error-border);
|
|
53
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/* ConfirmDialog — built on the native <dialog> element (see the component's
|
|
2
|
+
* own doc comment for why: content-sized, top-layer stacking, ::backdrop for
|
|
3
|
+
* free). Tokens only. */
|
|
4
|
+
|
|
5
|
+
.dialog {
|
|
6
|
+
border: none;
|
|
7
|
+
border-radius: var(--sv-radius-lg);
|
|
8
|
+
background: var(--sv-color-surface);
|
|
9
|
+
box-shadow: var(--sv-shadow-overlay);
|
|
10
|
+
padding: 0;
|
|
11
|
+
width: 22rem;
|
|
12
|
+
max-width: calc(100vw - var(--sv-space-8));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.dialog::backdrop {
|
|
16
|
+
background: var(--sv-color-scrim);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.body {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
gap: var(--sv-space-4);
|
|
23
|
+
padding: var(--sv-space-6);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.title {
|
|
27
|
+
margin: 0;
|
|
28
|
+
font-size: var(--sv-font-size-lg);
|
|
29
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
30
|
+
color: var(--sv-color-text-primary);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.message {
|
|
34
|
+
margin: 0;
|
|
35
|
+
font-size: var(--sv-font-size-sm);
|
|
36
|
+
color: var(--sv-color-text-muted);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.error {
|
|
40
|
+
margin: 0;
|
|
41
|
+
font-size: var(--sv-font-size-xs);
|
|
42
|
+
color: var(--sv-color-error-text);
|
|
43
|
+
padding: var(--sv-space-2) var(--sv-space-3);
|
|
44
|
+
border: 1px solid var(--sv-color-error-border);
|
|
45
|
+
border-radius: var(--sv-radius-sm);
|
|
46
|
+
background-color: var(--sv-color-error-surface);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.actions {
|
|
50
|
+
display: flex;
|
|
51
|
+
gap: var(--sv-space-3);
|
|
52
|
+
justify-content: flex-end;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Matches Button's own .button + .md structural rules exactly (padding,
|
|
56
|
+
* radius, font weight, focus-visible ring, disabled state, touch target,
|
|
57
|
+
* hover guard, active/pressed state) so it reads as the same control family
|
|
58
|
+
* — only the color scheme differs. Not built as an override of Button's own
|
|
59
|
+
* CSS module: cross-module class-order in the cascade isn't guaranteed, and
|
|
60
|
+
* an inline-style override would defeat Button's own :hover rule (inline
|
|
61
|
+
* styles always win over an external stylesheet, hover or not). A plain
|
|
62
|
+
* button with its own tokens sidesteps both problems. */
|
|
63
|
+
.destructiveConfirm {
|
|
64
|
+
display: inline-flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
gap: var(--sv-space-2);
|
|
68
|
+
padding: 13px var(--sv-space-4);
|
|
69
|
+
border: 1px solid transparent;
|
|
70
|
+
border-radius: var(--sv-radius-md);
|
|
71
|
+
font-family: var(--sv-font-family);
|
|
72
|
+
font-size: var(--sv-font-size-sm);
|
|
73
|
+
font-weight: var(--sv-font-weight-semibold);
|
|
74
|
+
line-height: 1;
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
touch-action: manipulation;
|
|
77
|
+
background-color: var(--sv-color-error-solid);
|
|
78
|
+
color: var(--sv-color-text-on-error);
|
|
79
|
+
transition: background-color 0.15s ease;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@media (pointer: coarse) {
|
|
83
|
+
.destructiveConfirm {
|
|
84
|
+
min-height: var(--sv-touch-target-min, 44px);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Derived via color-mix from the semantic token, not a raw --sv-red-800
|
|
89
|
+
primitive (the original .dangerButton pattern this replaces did reference
|
|
90
|
+
the primitive directly — fixed here, not carried forward). */
|
|
91
|
+
@media (hover: hover) {
|
|
92
|
+
.destructiveConfirm:hover:not(:disabled) {
|
|
93
|
+
background-color: color-mix(in srgb, var(--sv-color-error-solid) 85%, black);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.destructiveConfirm:active:not(:disabled) {
|
|
98
|
+
background-color: color-mix(in srgb, var(--sv-color-error-solid) 80%, black);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.destructiveConfirm:focus-visible {
|
|
102
|
+
outline: 2px solid var(--sv-color-focus-ring);
|
|
103
|
+
outline-offset: 2px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.destructiveConfirm:disabled {
|
|
107
|
+
opacity: 0.5;
|
|
108
|
+
cursor: not-allowed;
|
|
109
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* DatePicker — trigger styled to match Input's height/border/radius/focus
|
|
2
|
+
* ring for visual consistency as a form field. */
|
|
3
|
+
|
|
4
|
+
.trigger {
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
gap: var(--sv-space-2);
|
|
9
|
+
width: 100%;
|
|
10
|
+
padding: var(--sv-space-2) var(--sv-space-3);
|
|
11
|
+
font-family: var(--sv-font-family);
|
|
12
|
+
font-size: var(--sv-font-size-sm);
|
|
13
|
+
line-height: 1.5;
|
|
14
|
+
color: var(--sv-color-text-primary);
|
|
15
|
+
background-color: var(--sv-color-surface);
|
|
16
|
+
border: 1px solid var(--sv-color-border-strong);
|
|
17
|
+
border-radius: var(--sv-radius-md);
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
transition:
|
|
20
|
+
border-color 0.15s ease,
|
|
21
|
+
box-shadow 0.15s ease;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Same iOS auto-zoom fix as Input — see its own module.css for why. */
|
|
25
|
+
@media (pointer: coarse) {
|
|
26
|
+
.trigger {
|
|
27
|
+
font-size: var(--sv-font-size-md);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.trigger:focus-visible {
|
|
32
|
+
outline: none;
|
|
33
|
+
border-color: var(--sv-color-accent);
|
|
34
|
+
box-shadow: 0 0 0 2px var(--sv-color-focus-ring);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.trigger:disabled {
|
|
38
|
+
opacity: 0.5;
|
|
39
|
+
cursor: not-allowed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.triggerValue {
|
|
43
|
+
color: var(--sv-color-text-primary);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.triggerPlaceholder {
|
|
47
|
+
color: var(--sv-color-text-muted);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.popoverBody {
|
|
51
|
+
padding: var(--sv-space-3);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.drawerBody {
|
|
55
|
+
padding: var(--sv-space-4);
|
|
56
|
+
padding-bottom: max(var(--sv-space-4), env(safe-area-inset-bottom));
|
|
57
|
+
}
|
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
|
}
|