flet-box 1.0.7 → 1.0.9
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 +3 -0
- package/bin/cli.js +13 -0
- package/bin/commands/checkApi.js +13 -1
- package/bin/commands/checkUi.js +1369 -0
- package/bin/commands/create.js +49 -5
- package/bin/commands/createBundle.js +21 -0
- package/bin/commands/runServer.js +11 -2
- package/bin/utils/pwaShell.js +137 -0
- package/bin/utils/templates.js +1398 -443
- package/docs/README.md +4 -0
- package/docs/cli/README.md +63 -2
- package/docs/guides/README.md +2 -0
- package/docs/guides/clean-ui.md +211 -0
- package/docs/guides/minification-and-protection.md +1 -1
- package/docs/guides/mobile-and-platforms.md +31 -0
- package/docs/guides/online-updates.md +48 -0
- package/docs/tools/README.md +1 -1
- package/docs/tools/animation.md +1 -1
- package/docs/tools/markdown.md +3 -3
- package/docs/tools/theme.md +42 -1
- package/docs/widget/Accordion.md +3 -0
- package/docs/widget/AdMob.md +4 -1
- package/docs/widget/AdSense.md +3 -0
- package/docs/widget/AnimatedBox.md +6 -3
- package/docs/widget/AnimatedText.md +3 -0
- package/docs/widget/Audio.md +3 -0
- package/docs/widget/Avatar.md +3 -0
- package/docs/widget/Badge.md +3 -0
- package/docs/widget/Button.md +3 -0
- package/docs/widget/COMMON_PROPS.md +52 -0
- package/docs/widget/Card.md +3 -0
- package/docs/widget/Carousel.md +3 -0
- package/docs/widget/Chart.md +3 -0
- package/docs/widget/Checkbox.md +3 -0
- package/docs/widget/Chip.md +3 -0
- package/docs/widget/CircularBar.md +3 -0
- package/docs/widget/CircularChart.md +3 -0
- package/docs/widget/CodeViewer.md +3 -0
- package/docs/widget/Column.md +3 -0
- package/docs/widget/Container.md +3 -0
- package/docs/widget/DataTable.md +3 -0
- package/docs/widget/Divider.md +3 -0
- package/docs/widget/DraggBox.md +3 -0
- package/docs/widget/Dropdown.md +3 -0
- package/docs/widget/DroppBox.md +3 -0
- package/docs/widget/FloatingActionButton.md +3 -0
- package/docs/widget/GridView.md +3 -0
- package/docs/widget/Icon.md +3 -0
- package/docs/widget/Image.md +3 -0
- package/docs/widget/Input.md +3 -0
- package/docs/widget/Inspector.md +2 -2
- package/docs/widget/InstallButton.md +3 -0
- package/docs/widget/ListTile.md +3 -0
- package/docs/widget/ListView.md +3 -0
- package/docs/widget/Maps.md +3 -0
- package/docs/widget/Markdown.md +3 -0
- package/docs/widget/MatrixRain.md +3 -0
- package/docs/widget/Pagination.md +3 -0
- package/docs/widget/ParallaxBox.md +3 -0
- package/docs/widget/ProgressBar.md +3 -0
- package/docs/widget/QRCode.md +3 -0
- package/docs/widget/README.md +2 -0
- package/docs/widget/Radio.md +4 -1
- package/docs/widget/Rating.md +3 -0
- package/docs/widget/Row.md +3 -0
- package/docs/widget/Skeleton.md +3 -0
- package/docs/widget/Slider.md +3 -0
- package/docs/widget/Stack.md +3 -0
- package/docs/widget/Stepper.md +3 -0
- package/docs/widget/Switch.md +3 -0
- package/docs/widget/Tabs.md +5 -5
- package/docs/widget/Text.md +3 -0
- package/docs/widget/Tooltip.md +3 -0
- package/docs/widget/TreeView.md +3 -0
- package/docs/widget/Video.md +3 -0
- package/package.json +2 -2
- package/src/animations/AnimatedBox.js +4 -1
- package/src/animations/MatrixRain.js +3 -3
- package/src/index.d.ts +64 -8
- package/src/index.js +1 -0
- package/src/navigations/AppBar.js +7 -12
- package/src/navigations/BottomNavigation.js +8 -13
- package/src/navigations/CollapsibleSideBar.js +30 -8
- package/src/navigations/Drawer.js +16 -14
- package/src/navigations/DrawerItem.js +3 -1
- package/src/navigations/Scaffold.js +26 -12
- package/src/navigations/Tabs.js +104 -59
- package/src/tools/color.js +54 -0
- package/src/tools/index.js +9 -1
- package/src/tools/scale.js +41 -0
- package/src/tools/shadow.js +20 -0
- package/src/utils/index.js +1 -0
- package/src/utils/readableInk.js +19 -0
- package/src/utils/syntaxHighlight.js +78 -10
- package/src/utils/themes.js +265 -58
- package/src/utils/units.js +33 -11
- package/src/widget-factory/effects.js +56 -5
- package/src/widget-factory/processProps.js +10 -1
- package/src/widget-factory/remTool.js +15 -29
- package/src/widget-factory/stackPosition.js +18 -3
- package/src/widget-factory/translateProps.js +28 -0
- package/src/widgets/Accordion.js +20 -23
- package/src/widgets/AdMob.js +7 -2
- package/src/widgets/Avatar.js +18 -12
- package/src/widgets/Badge.js +3 -2
- package/src/widgets/BottomSheet.js +14 -13
- package/src/widgets/Button.js +69 -30
- package/src/widgets/Card.js +6 -2
- package/src/widgets/Carousel.js +12 -10
- package/src/widgets/Chart.js +28 -5
- package/src/widgets/Checkbox.js +76 -18
- package/src/widgets/Chip.js +31 -10
- package/src/widgets/CircularBar.js +7 -7
- package/src/widgets/CircularChart.js +26 -8
- package/src/widgets/CodeViewer.js +93 -20
- package/src/widgets/DataTable.js +17 -11
- package/src/widgets/DraggBox.js +2 -1
- package/src/widgets/Dropdown.js +26 -13
- package/src/widgets/DroppBox.js +4 -2
- package/src/widgets/FloatingActionButton.js +13 -7
- package/src/widgets/Icon.js +3 -1
- package/src/widgets/Input.js +30 -23
- package/src/widgets/Inspector.js +54 -1
- package/src/widgets/InstallButton.js +6 -4
- package/src/widgets/ListTile.js +4 -3
- package/src/widgets/ListView.js +30 -12
- package/src/widgets/Markdown.js +37 -26
- package/src/widgets/Modal.js +14 -11
- package/src/widgets/Pagination.js +16 -11
- package/src/widgets/ProgressBar.js +6 -4
- package/src/widgets/Radio.js +73 -15
- package/src/widgets/Rating.js +24 -6
- package/src/widgets/Skeleton.js +31 -31
- package/src/widgets/Slider.js +29 -18
- package/src/widgets/SnackBar.js +35 -39
- package/src/widgets/Stepper.js +13 -9
- package/src/widgets/Switch.js +59 -18
- package/src/widgets/Tooltip.js +12 -10
- package/src/widgets/TreeView.js +27 -15
package/src/widgets/Skeleton.js
CHANGED
|
@@ -6,6 +6,8 @@ import { WidgetFactory } from "../widget-factory/index.js";
|
|
|
6
6
|
import { colors } from "../utils/themes.js";
|
|
7
7
|
import { injectKeyframes } from "../utils/styleInjector.js";
|
|
8
8
|
|
|
9
|
+
import { toREM } from "../utils/units.js";
|
|
10
|
+
|
|
9
11
|
export const Skeleton = (props) => {
|
|
10
12
|
const {
|
|
11
13
|
variant = "text", // 'text', 'circular', 'avatar', 'image', 'card', 'listTile', 'button'
|
|
@@ -43,8 +45,12 @@ export const Skeleton = (props) => {
|
|
|
43
45
|
return { width: "100%", height: 120, borderRadius: 12 };
|
|
44
46
|
case "listTile":
|
|
45
47
|
return { width: "100%", height: 56, borderRadius: 8 };
|
|
48
|
+
// The shape a button actually has: 40 units tall (the Fitts floor the Button
|
|
49
|
+
// preset uses) with the Button's own 16-unit corner, not an invented 36x24
|
|
50
|
+
// pill — a skeleton that does not match the control it replaces reads as a
|
|
51
|
+
// layout shift when the real one loads.
|
|
46
52
|
case "button":
|
|
47
|
-
return { width: 120, height:
|
|
53
|
+
return { width: 120, height: 40, borderRadius: 16 };
|
|
48
54
|
default:
|
|
49
55
|
return { width: "100%", height: 20, borderRadius: 4 };
|
|
50
56
|
}
|
|
@@ -79,14 +85,11 @@ export const Skeleton = (props) => {
|
|
|
79
85
|
if (variant === "button") {
|
|
80
86
|
return WidgetFactory({
|
|
81
87
|
tag: "div",
|
|
82
|
-
width:
|
|
88
|
+
width: toREM(finalWidth),
|
|
83
89
|
height:
|
|
84
|
-
|
|
90
|
+
toREM(finalHeight),
|
|
85
91
|
backgroundColor: bgColor,
|
|
86
|
-
borderRadius:
|
|
87
|
-
typeof finalBorderRadius === "number"
|
|
88
|
-
? `${finalBorderRadius}px`
|
|
89
|
-
: finalBorderRadius,
|
|
92
|
+
borderRadius: toREM(finalBorderRadius),
|
|
90
93
|
...getAnimationStyle(),
|
|
91
94
|
});
|
|
92
95
|
}
|
|
@@ -97,14 +100,14 @@ export const Skeleton = (props) => {
|
|
|
97
100
|
tag: "div",
|
|
98
101
|
display: "flex",
|
|
99
102
|
alignItems: "center",
|
|
100
|
-
gap:
|
|
103
|
+
gap: 12,
|
|
101
104
|
width: "100%",
|
|
102
105
|
});
|
|
103
106
|
|
|
104
107
|
const avatar = WidgetFactory({
|
|
105
108
|
tag: "div",
|
|
106
|
-
width:
|
|
107
|
-
height:
|
|
109
|
+
width: 40,
|
|
110
|
+
height: 40,
|
|
108
111
|
backgroundColor: bgColor,
|
|
109
112
|
borderRadius: "50%",
|
|
110
113
|
flexShrink: 0,
|
|
@@ -117,24 +120,24 @@ export const Skeleton = (props) => {
|
|
|
117
120
|
flex: 1,
|
|
118
121
|
display: "flex",
|
|
119
122
|
flexDirection: "column",
|
|
120
|
-
gap:
|
|
123
|
+
gap: 8,
|
|
121
124
|
});
|
|
122
125
|
|
|
123
126
|
const line1 = WidgetFactory({
|
|
124
127
|
tag: "div",
|
|
125
128
|
width: "80%",
|
|
126
|
-
height:
|
|
129
|
+
height: 14,
|
|
127
130
|
backgroundColor: bgColor,
|
|
128
|
-
borderRadius:
|
|
131
|
+
borderRadius: 4,
|
|
129
132
|
...getAnimationStyle(),
|
|
130
133
|
});
|
|
131
134
|
|
|
132
135
|
const line2 = WidgetFactory({
|
|
133
136
|
tag: "div",
|
|
134
137
|
width: "60%",
|
|
135
|
-
height:
|
|
138
|
+
height: 12,
|
|
136
139
|
backgroundColor: bgColor,
|
|
137
|
-
borderRadius:
|
|
140
|
+
borderRadius: 4,
|
|
138
141
|
...getAnimationStyle(),
|
|
139
142
|
});
|
|
140
143
|
|
|
@@ -151,16 +154,16 @@ export const Skeleton = (props) => {
|
|
|
151
154
|
tag: "div",
|
|
152
155
|
display: "flex",
|
|
153
156
|
flexDirection: "column",
|
|
154
|
-
gap:
|
|
157
|
+
gap: 12,
|
|
155
158
|
width: "100%",
|
|
156
159
|
});
|
|
157
160
|
|
|
158
161
|
const imageArea = WidgetFactory({
|
|
159
162
|
tag: "div",
|
|
160
163
|
width: "100%",
|
|
161
|
-
height:
|
|
164
|
+
height: 120,
|
|
162
165
|
backgroundColor: bgColor,
|
|
163
|
-
borderRadius:
|
|
166
|
+
borderRadius: 8,
|
|
164
167
|
...getAnimationStyle(),
|
|
165
168
|
});
|
|
166
169
|
container.appendChild(imageArea);
|
|
@@ -169,25 +172,25 @@ export const Skeleton = (props) => {
|
|
|
169
172
|
tag: "div",
|
|
170
173
|
display: "flex",
|
|
171
174
|
flexDirection: "column",
|
|
172
|
-
gap:
|
|
173
|
-
|
|
175
|
+
gap: 8,
|
|
176
|
+
paddingHorizontal: 8,
|
|
174
177
|
});
|
|
175
178
|
|
|
176
179
|
const line1 = WidgetFactory({
|
|
177
180
|
tag: "div",
|
|
178
181
|
width: "90%",
|
|
179
|
-
height:
|
|
182
|
+
height: 16,
|
|
180
183
|
backgroundColor: bgColor,
|
|
181
|
-
borderRadius:
|
|
184
|
+
borderRadius: 4,
|
|
182
185
|
...getAnimationStyle(),
|
|
183
186
|
});
|
|
184
187
|
|
|
185
188
|
const line2 = WidgetFactory({
|
|
186
189
|
tag: "div",
|
|
187
190
|
width: "70%",
|
|
188
|
-
height:
|
|
191
|
+
height: 12,
|
|
189
192
|
backgroundColor: bgColor,
|
|
190
|
-
borderRadius:
|
|
193
|
+
borderRadius: 4,
|
|
191
194
|
...getAnimationStyle(),
|
|
192
195
|
});
|
|
193
196
|
|
|
@@ -201,14 +204,11 @@ export const Skeleton = (props) => {
|
|
|
201
204
|
// Simple variants (text, circular, avatar, image)
|
|
202
205
|
return WidgetFactory({
|
|
203
206
|
tag: "div",
|
|
204
|
-
width:
|
|
207
|
+
width: toREM(finalWidth),
|
|
205
208
|
height:
|
|
206
|
-
|
|
209
|
+
toREM(finalHeight),
|
|
207
210
|
backgroundColor: bgColor,
|
|
208
|
-
borderRadius:
|
|
209
|
-
typeof finalBorderRadius === "number"
|
|
210
|
-
? `${finalBorderRadius}px`
|
|
211
|
-
: finalBorderRadius,
|
|
211
|
+
borderRadius: toREM(finalBorderRadius),
|
|
212
212
|
...getAnimationStyle(),
|
|
213
213
|
});
|
|
214
214
|
};
|
|
@@ -219,7 +219,7 @@ export const Skeleton = (props) => {
|
|
|
219
219
|
widgetName: "Skeleton",
|
|
220
220
|
display: "flex",
|
|
221
221
|
flexDirection: "column",
|
|
222
|
-
gap:
|
|
222
|
+
gap: toREM(gap),
|
|
223
223
|
width: "100%",
|
|
224
224
|
});
|
|
225
225
|
|
package/src/widgets/Slider.js
CHANGED
|
@@ -12,6 +12,9 @@ import { injectKeyframes } from "../utils/styleInjector.js";
|
|
|
12
12
|
* Props owned by the Slider's closure state. Routed to the widget's `update`
|
|
13
13
|
* and never to the factory's (see `composeUpdate`).
|
|
14
14
|
*/
|
|
15
|
+
import { toREM } from "../utils/units.js";
|
|
16
|
+
import { TAP_MINIMUM } from "../tools/index.js";
|
|
17
|
+
|
|
15
18
|
const SLIDER_PROP_KEYS = [
|
|
16
19
|
"value", "min", "max", "step", "disabled",
|
|
17
20
|
"width", "height", "thumbSize",
|
|
@@ -32,7 +35,7 @@ export const Slider = (props) => {
|
|
|
32
35
|
// Appearance
|
|
33
36
|
width = "100%",
|
|
34
37
|
height = 4,
|
|
35
|
-
thumbSize =
|
|
38
|
+
thumbSize = 24,
|
|
36
39
|
color = colors.primary,
|
|
37
40
|
trackColor = colors.border,
|
|
38
41
|
thumbColor = "#fff",
|
|
@@ -45,7 +48,9 @@ export const Slider = (props) => {
|
|
|
45
48
|
showMarks = false,
|
|
46
49
|
marks = [], // ← implemented
|
|
47
50
|
// Visual effects
|
|
48
|
-
|
|
51
|
+
// Off by default: diagonal stripes turn a value control into a progress bar
|
|
52
|
+
// that is saying something, and a text-size slider is not saying anything.
|
|
53
|
+
striped = false,
|
|
49
54
|
animatedStripes = false,
|
|
50
55
|
stripeColor = "rgba(255,255,255,0.25)",
|
|
51
56
|
glow = false,
|
|
@@ -72,10 +77,10 @@ export const Slider = (props) => {
|
|
|
72
77
|
let displayPercent = inverted ? 1 - rawPercent : rawPercent;
|
|
73
78
|
|
|
74
79
|
let isHorizontal = orientation === "horizontal";
|
|
75
|
-
let
|
|
76
|
-
let
|
|
77
|
-
|
|
78
|
-
let thumbSizeNum = typeof thumbSize === "number" ? thumbSize :
|
|
80
|
+
let trackSize = isHorizontal ? height : width;
|
|
81
|
+
let trackLength =
|
|
82
|
+
toREM(trackSize);
|
|
83
|
+
let thumbSizeNum = typeof thumbSize === "number" ? thumbSize : 24;
|
|
79
84
|
|
|
80
85
|
// Positioning retries while the track still measures 0 (hidden parent, not laid
|
|
81
86
|
// out yet). Both handles are tracked so unmounting can stop them: an untracked
|
|
@@ -109,11 +114,11 @@ export const Slider = (props) => {
|
|
|
109
114
|
valueDisplay = WidgetFactory({
|
|
110
115
|
tag: "div",
|
|
111
116
|
textContent: `${valuePrefix}${currentValue}${valueSuffix}`,
|
|
112
|
-
fontSize:
|
|
117
|
+
fontSize: 14,
|
|
113
118
|
color: colors.textSecondary,
|
|
114
119
|
textAlign: "center",
|
|
115
|
-
marginBottom: isHorizontal ?
|
|
116
|
-
marginRight: !isHorizontal ?
|
|
120
|
+
marginBottom: isHorizontal ? 4 : 0,
|
|
121
|
+
marginRight: !isHorizontal ? 4 : 0,
|
|
117
122
|
});
|
|
118
123
|
container.appendChild(valueDisplay);
|
|
119
124
|
}
|
|
@@ -125,12 +130,15 @@ export const Slider = (props) => {
|
|
|
125
130
|
...(isHorizontal
|
|
126
131
|
? {
|
|
127
132
|
width: "100%",
|
|
128
|
-
height:
|
|
133
|
+
height: trackLength,
|
|
134
|
+
// The rail is 4 units of paint; the surface a thumb can grab is 40.
|
|
135
|
+
minHeight: TAP_MINIMUM,
|
|
129
136
|
display: "flex",
|
|
130
137
|
alignItems: "center",
|
|
131
138
|
}
|
|
132
139
|
: {
|
|
133
140
|
height: "100%", // for vertical, the wrapper takes the full height
|
|
141
|
+
minWidth: TAP_MINIMUM,
|
|
134
142
|
display: "flex",
|
|
135
143
|
justifyContent: "center",
|
|
136
144
|
}),
|
|
@@ -144,14 +152,14 @@ export const Slider = (props) => {
|
|
|
144
152
|
...(isHorizontal
|
|
145
153
|
? {
|
|
146
154
|
width: "100%",
|
|
147
|
-
height:
|
|
155
|
+
height: trackLength,
|
|
148
156
|
}
|
|
149
157
|
: {
|
|
150
|
-
width:
|
|
158
|
+
width: trackLength,
|
|
151
159
|
height: "100%",
|
|
152
160
|
}),
|
|
153
161
|
backgroundColor: trackColor,
|
|
154
|
-
borderRadius:
|
|
162
|
+
borderRadius: trackLength,
|
|
155
163
|
cursor: disabled ? "not-allowed" : "pointer",
|
|
156
164
|
});
|
|
157
165
|
wrapper.appendChild(track);
|
|
@@ -161,7 +169,7 @@ export const Slider = (props) => {
|
|
|
161
169
|
tag: "div",
|
|
162
170
|
position: "absolute",
|
|
163
171
|
backgroundColor: color,
|
|
164
|
-
borderRadius:
|
|
172
|
+
borderRadius: trackLength,
|
|
165
173
|
transition: disabled ? "none" : "width 0.1s ease, height 0.1s ease",
|
|
166
174
|
...(isHorizontal
|
|
167
175
|
? {
|
|
@@ -191,8 +199,8 @@ export const Slider = (props) => {
|
|
|
191
199
|
const thumb = WidgetFactory({
|
|
192
200
|
tag: "div",
|
|
193
201
|
position: "absolute",
|
|
194
|
-
width:
|
|
195
|
-
height:
|
|
202
|
+
width: toREM(thumbSizeNum),
|
|
203
|
+
height: toREM(thumbSizeNum),
|
|
196
204
|
backgroundColor: thumbColor,
|
|
197
205
|
borderRadius: "50%",
|
|
198
206
|
boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
|
|
@@ -350,12 +358,15 @@ export const Slider = (props) => {
|
|
|
350
358
|
}
|
|
351
359
|
if (isHorizontal) {
|
|
352
360
|
const left = displayPercent * trackRect.width;
|
|
353
|
-
thumb
|
|
361
|
+
// The half-thumb comes from the rendered box, not from `thumbSize`: that
|
|
362
|
+
// number is a framework unit and the handle is `rem`, so at a raised font
|
|
363
|
+
// size the handle is wider than the arithmetic says and sits off-centre.
|
|
364
|
+
thumb.style.left = `${left - thumb.offsetWidth / 2}px`;
|
|
354
365
|
thumb.style.top = "50%";
|
|
355
366
|
thumb.style.transform = "translateY(-50%)";
|
|
356
367
|
} else {
|
|
357
368
|
const bottom = displayPercent * trackRect.height;
|
|
358
|
-
thumb.style.bottom = `${bottom -
|
|
369
|
+
thumb.style.bottom = `${bottom - thumb.offsetHeight / 2}px`;
|
|
359
370
|
thumb.style.left = "50%";
|
|
360
371
|
thumb.style.transform = "translateX(-50%)";
|
|
361
372
|
}
|
package/src/widgets/SnackBar.js
CHANGED
|
@@ -5,27 +5,9 @@
|
|
|
5
5
|
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
6
|
import { border } from "../tools/index.js";
|
|
7
7
|
import { colors } from "../utils/themes.js";
|
|
8
|
+
import { readableInk } from "../utils/readableInk.js";
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
const FALLBACK = {
|
|
11
|
-
gray800: "#1e293b",
|
|
12
|
-
primary: "#6366f1",
|
|
13
|
-
success: "#10b981",
|
|
14
|
-
danger: "#ef4444",
|
|
15
|
-
warning: "#f59e0b",
|
|
16
|
-
info: "#3b82f6",
|
|
17
|
-
gray900: "#0f172a",
|
|
18
|
-
white: "#ffffff",
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// Safe function to get theme color
|
|
22
|
-
const getThemeColor = (name, fallback) => {
|
|
23
|
-
try {
|
|
24
|
-
return (colors && colors[name]) || fallback;
|
|
25
|
-
} catch {
|
|
26
|
-
return fallback;
|
|
27
|
-
}
|
|
28
|
-
};
|
|
10
|
+
import { toREM } from "../utils/units.js";
|
|
29
11
|
|
|
30
12
|
/**
|
|
31
13
|
* SnackBar - Notificación temporal
|
|
@@ -54,7 +36,20 @@ export function SnackBar(options) {
|
|
|
54
36
|
onClose,
|
|
55
37
|
} = options;
|
|
56
38
|
|
|
57
|
-
//
|
|
39
|
+
// A preset plate is a solid signal colour, and the theme's ink is not always
|
|
40
|
+
// readable on it: in dark mode `colors.text` is near-white, which measured
|
|
41
|
+
// 1.9:1 on the green. The label follows the plate it is painted on.
|
|
42
|
+
const plateInk = (bg) => {
|
|
43
|
+
if (bg === colors.primary) return colors.onPrimary;
|
|
44
|
+
if (bg === colors.secondary) return colors.onSecondary;
|
|
45
|
+
return readableInk(bg, colors.text);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const successInk = plateInk(colors.success);
|
|
49
|
+
const dangerInk = plateInk(colors.danger);
|
|
50
|
+
const warningInk = plateInk(colors.warning);
|
|
51
|
+
const infoInk = plateInk(colors.info);
|
|
52
|
+
|
|
58
53
|
const presets = {
|
|
59
54
|
normal: {
|
|
60
55
|
bg: colors.surface,
|
|
@@ -63,42 +58,43 @@ export function SnackBar(options) {
|
|
|
63
58
|
},
|
|
64
59
|
success: {
|
|
65
60
|
bg: colors.success,
|
|
66
|
-
text:
|
|
67
|
-
action:
|
|
61
|
+
text: successInk,
|
|
62
|
+
action: successInk,
|
|
68
63
|
},
|
|
69
64
|
error: {
|
|
70
65
|
bg: colors.danger,
|
|
71
|
-
text:
|
|
72
|
-
action:
|
|
66
|
+
text: dangerInk,
|
|
67
|
+
action: dangerInk,
|
|
73
68
|
},
|
|
74
69
|
warning: {
|
|
75
70
|
bg: colors.warning,
|
|
76
|
-
text:
|
|
77
|
-
action:
|
|
71
|
+
text: warningInk,
|
|
72
|
+
action: warningInk,
|
|
78
73
|
},
|
|
79
74
|
info: {
|
|
80
75
|
bg: colors.info,
|
|
81
|
-
text:
|
|
82
|
-
action:
|
|
76
|
+
text: infoInk,
|
|
77
|
+
action: infoInk,
|
|
83
78
|
},
|
|
84
79
|
};
|
|
85
80
|
|
|
86
81
|
const preset = presets[type] || presets.normal;
|
|
87
82
|
const finalBg = customBg || preset.bg;
|
|
88
|
-
const finalText = customText || preset.text;
|
|
89
|
-
const finalAction =
|
|
83
|
+
const finalText = customText || (customBg ? plateInk(finalBg) : preset.text);
|
|
84
|
+
const finalAction =
|
|
85
|
+
customAction || (customBg ? finalText : preset.action);
|
|
90
86
|
|
|
91
87
|
// Createste main container
|
|
92
88
|
const snackbar = WidgetFactory({
|
|
93
89
|
tag: "div",
|
|
94
90
|
position: "fixed",
|
|
95
|
-
left:
|
|
91
|
+
left: toREM(margin),
|
|
96
92
|
// borderRadius: borderRadius,
|
|
97
|
-
right:
|
|
98
|
-
[position]:
|
|
93
|
+
right: toREM(margin),
|
|
94
|
+
[position]: toREM(margin),
|
|
99
95
|
backgroundColor: finalBg,
|
|
100
96
|
borderRadius:
|
|
101
|
-
|
|
97
|
+
toREM(borderRadius),
|
|
102
98
|
padding: padding,
|
|
103
99
|
border: border(1, "solid", colors.gray100),
|
|
104
100
|
zIndex: zIndex,
|
|
@@ -109,8 +105,8 @@ export function SnackBar(options) {
|
|
|
109
105
|
opacity: 0,
|
|
110
106
|
transform:
|
|
111
107
|
position === "bottom"
|
|
112
|
-
? `translateY(calc(100% + ${margin}
|
|
113
|
-
: `translateY(calc(-100% - ${margin}
|
|
108
|
+
? `translateY(calc(100% + ${toREM(margin)}))`
|
|
109
|
+
: `translateY(calc(-100% - ${toREM(margin)}))`,
|
|
114
110
|
transition: `opacity ${animationDuration}ms ease, transform ${animationDuration}ms ease`,
|
|
115
111
|
display: "flex",
|
|
116
112
|
alignItems: "center",
|
|
@@ -156,8 +152,8 @@ export function SnackBar(options) {
|
|
|
156
152
|
snackbar.style.opacity = "0";
|
|
157
153
|
snackbar.style.transform =
|
|
158
154
|
position === "bottom"
|
|
159
|
-
? `translateY(calc(100% + ${margin}
|
|
160
|
-
: `translateY(calc(-100% - ${margin}
|
|
155
|
+
? `translateY(calc(100% + ${toREM(margin)}))`
|
|
156
|
+
: `translateY(calc(-100% - ${toREM(margin)}))`;
|
|
161
157
|
setTimeout(() => {
|
|
162
158
|
if (snackbar.parentNode) snackbar.parentNode.removeChild(snackbar);
|
|
163
159
|
if (onClose) onClose();
|
package/src/widgets/Stepper.js
CHANGED
|
@@ -11,6 +11,8 @@ import { Icon } from "./Icon.js";
|
|
|
11
11
|
import { Button } from "./Button.js";
|
|
12
12
|
import { colors } from "../utils/themes.js";
|
|
13
13
|
|
|
14
|
+
import { toREM } from "../utils/units.js";
|
|
15
|
+
|
|
14
16
|
export const Stepper = (props) => {
|
|
15
17
|
const {
|
|
16
18
|
steps = [],
|
|
@@ -32,7 +34,7 @@ export const Stepper = (props) => {
|
|
|
32
34
|
borderColor = colors.border,
|
|
33
35
|
borderWidth = 1,
|
|
34
36
|
shadow,
|
|
35
|
-
padding =
|
|
37
|
+
padding = 24,
|
|
36
38
|
margin = 0,
|
|
37
39
|
width = "100%",
|
|
38
40
|
...rest
|
|
@@ -85,7 +87,7 @@ export const Stepper = (props) => {
|
|
|
85
87
|
bgColor: isActive
|
|
86
88
|
? colors.primary
|
|
87
89
|
: isCompleted
|
|
88
|
-
? colors.
|
|
90
|
+
? colors.successPlate
|
|
89
91
|
: colors.gray300,
|
|
90
92
|
justifyContent: "center",
|
|
91
93
|
alignItems: "center",
|
|
@@ -95,7 +97,9 @@ export const Stepper = (props) => {
|
|
|
95
97
|
text: String(idx + 1),
|
|
96
98
|
size: 14,
|
|
97
99
|
weight: "bold",
|
|
98
|
-
|
|
100
|
+
// On the grey step plate, `textSecondary` measured 4.07:1 in
|
|
101
|
+
// dark; gray700 stays muted and clears AA on both themes.
|
|
102
|
+
color: isActive ? colors.onPrimary : colors.gray700,
|
|
99
103
|
}),
|
|
100
104
|
});
|
|
101
105
|
} else if (variant === "numbers") {
|
|
@@ -103,13 +107,13 @@ export const Stepper = (props) => {
|
|
|
103
107
|
text: String(idx + 1),
|
|
104
108
|
size: 16,
|
|
105
109
|
weight: "bold",
|
|
106
|
-
color: isActive ? colors.primary : colors.
|
|
110
|
+
color: isActive ? colors.primary : colors.gray700,
|
|
107
111
|
});
|
|
108
112
|
} else {
|
|
109
113
|
indicator = Icon({
|
|
110
114
|
name: step.icon || "circle",
|
|
111
115
|
size: 24,
|
|
112
|
-
color: isActive ? colors.primary : colors.
|
|
116
|
+
color: isActive ? colors.primary : colors.gray500,
|
|
113
117
|
});
|
|
114
118
|
}
|
|
115
119
|
|
|
@@ -215,12 +219,12 @@ export const Stepper = (props) => {
|
|
|
215
219
|
|
|
216
220
|
// Build style for main container using WidgetFactory
|
|
217
221
|
let style = {
|
|
218
|
-
width:
|
|
222
|
+
width: toREM(width),
|
|
219
223
|
backgroundColor: bgColor,
|
|
220
224
|
borderRadius:
|
|
221
|
-
|
|
222
|
-
padding:
|
|
223
|
-
margin:
|
|
225
|
+
toREM(borderRadius),
|
|
226
|
+
padding: toREM(padding),
|
|
227
|
+
margin: toREM(margin),
|
|
224
228
|
display: "flex",
|
|
225
229
|
flexDirection: "column",
|
|
226
230
|
gap: "24px",
|
package/src/widgets/Switch.js
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* @description iOS-style toggle switch with controlled/uncontrolled state.
|
|
4
4
|
*/
|
|
5
5
|
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
|
+
import { Text } from "./Text.js";
|
|
6
7
|
import { colors } from "../utils/themes.js";
|
|
8
|
+
import { toREM } from "../utils/units.js";
|
|
9
|
+
import { PILL, TAP_MINIMUM, LABEL_TEXT } from "../tools/index.js";
|
|
7
10
|
|
|
8
11
|
export const Switch = (props) => {
|
|
9
12
|
const {
|
|
@@ -13,6 +16,7 @@ export const Switch = (props) => {
|
|
|
13
16
|
disabled = false,
|
|
14
17
|
size = "medium",
|
|
15
18
|
activeColor = colors.success,
|
|
19
|
+
label = null,
|
|
16
20
|
...rest
|
|
17
21
|
} = props;
|
|
18
22
|
|
|
@@ -20,13 +24,19 @@ export const Switch = (props) => {
|
|
|
20
24
|
// onToggle kept as alias for backward compatibility
|
|
21
25
|
const onPress = onPressProp || onToggle;
|
|
22
26
|
|
|
23
|
-
// Sizes
|
|
27
|
+
// Sizes. Track height is knob + 2 x pad, so the knob can never be asked to sit
|
|
28
|
+
// outside the pill it lives in.
|
|
24
29
|
const sizes = {
|
|
25
|
-
small: { width: 40, height: 24, knob:
|
|
26
|
-
medium: { width:
|
|
27
|
-
large: { width:
|
|
30
|
+
small: { width: 40, height: 24, knob: 16 },
|
|
31
|
+
medium: { width: 48, height: 32, knob: 24 },
|
|
32
|
+
large: { width: 64, height: 40, knob: 32 },
|
|
28
33
|
};
|
|
29
34
|
const sz = sizes[size] || sizes.medium;
|
|
35
|
+
const PAD = 4;
|
|
36
|
+
/** The knob's travel, in framework units. It has to be rem, like the track: a
|
|
37
|
+
* `px` offset here slides the knob short of the edge the moment the reader
|
|
38
|
+
* raises their font and the (rem) track grows around it. */
|
|
39
|
+
const travel = () => toREM(sz.width - sz.knob - PAD * 2);
|
|
30
40
|
|
|
31
41
|
let internalValue = value;
|
|
32
42
|
let switchRef = null;
|
|
@@ -37,9 +47,7 @@ export const Switch = (props) => {
|
|
|
37
47
|
switchRef.style.backgroundColor = newValue ? activeColor : colors.gray300;
|
|
38
48
|
}
|
|
39
49
|
if (knobRef) {
|
|
40
|
-
knobRef.style.transform = newValue
|
|
41
|
-
? `translateX(${sz.width - sz.knob - 4}px)`
|
|
42
|
-
: "translateX(0)";
|
|
50
|
+
knobRef.style.transform = newValue ? `translateX(${travel()})` : "translateX(0)";
|
|
43
51
|
}
|
|
44
52
|
};
|
|
45
53
|
|
|
@@ -49,32 +57,65 @@ export const Switch = (props) => {
|
|
|
49
57
|
height: sz.knob,
|
|
50
58
|
backgroundColor: "#ffffff",
|
|
51
59
|
borderRadius: "50%",
|
|
52
|
-
transform: internalValue
|
|
53
|
-
? `translateX(${sz.width - sz.knob - 4}px)`
|
|
54
|
-
: "translateX(0)",
|
|
60
|
+
transform: internalValue ? `translateX(${travel()})` : "translateX(0)",
|
|
55
61
|
transition: "transform 0.2s ease",
|
|
56
62
|
boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
|
|
57
63
|
});
|
|
58
64
|
knobRef = knob;
|
|
59
65
|
|
|
60
|
-
//
|
|
61
|
-
const
|
|
62
|
-
widgetName: "Switch",
|
|
66
|
+
// The visible pill. Everything the reader sees is painted here.
|
|
67
|
+
const track = WidgetFactory({
|
|
63
68
|
width: sz.width,
|
|
64
69
|
height: sz.height,
|
|
65
70
|
backgroundColor: internalValue ? activeColor : colors.gray300,
|
|
66
|
-
borderRadius:
|
|
71
|
+
borderRadius: PILL,
|
|
67
72
|
display: "flex",
|
|
68
73
|
alignItems: "center",
|
|
69
|
-
padding:
|
|
74
|
+
padding: PAD,
|
|
70
75
|
transition: "all 0.2s ease",
|
|
71
|
-
cursor: disabled ? "not-allowed" : "pointer",
|
|
72
|
-
opacity: disabled ? 0.5 : 1,
|
|
73
76
|
boxSizing: "border-box",
|
|
74
77
|
child: knob,
|
|
78
|
+
});
|
|
79
|
+
switchRef = track;
|
|
80
|
+
|
|
81
|
+
// The pressable box is the track's parent, not the track: a 32-unit pill is not
|
|
82
|
+
// a Fitts target, and stretching the pill to 40 would make it look like a
|
|
83
|
+
// button. The hit area grows, the switch does not.
|
|
84
|
+
//
|
|
85
|
+
// With a `label` the box grows further into the whole row, because a reader
|
|
86
|
+
// aiming at the words must still land on the toggle: the label is part of the
|
|
87
|
+
// control, not a caption beside it.
|
|
88
|
+
const isLabelled = label !== null && label !== undefined && label !== "";
|
|
89
|
+
|
|
90
|
+
const switchContainer = WidgetFactory({
|
|
91
|
+
widgetName: "Switch",
|
|
92
|
+
display: "flex",
|
|
93
|
+
flexDirection: "row",
|
|
94
|
+
alignItems: "center",
|
|
95
|
+
justifyContent: isLabelled ? "space-between" : "center",
|
|
96
|
+
gap: 12,
|
|
97
|
+
width: isLabelled ? "100%" : undefined,
|
|
98
|
+
minHeight: TAP_MINIMUM,
|
|
99
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
100
|
+
opacity: disabled ? 0.5 : 1,
|
|
101
|
+
// The root carries the click, so the default press effect scaled whatever
|
|
102
|
+
// the root happens to be — with a label that is the whole row, and the words
|
|
103
|
+
// shrank with the switch. The knob's slide is this control's feedback; the
|
|
104
|
+
// row must not behave like a button.
|
|
105
|
+
disableTransform: true,
|
|
106
|
+
child: isLabelled
|
|
107
|
+
? [
|
|
108
|
+
Text({
|
|
109
|
+
text: label,
|
|
110
|
+
size: LABEL_TEXT,
|
|
111
|
+
fontWeight: 500,
|
|
112
|
+
color: disabled ? colors.textDisabled : colors.text,
|
|
113
|
+
}),
|
|
114
|
+
track,
|
|
115
|
+
]
|
|
116
|
+
: track,
|
|
75
117
|
...rest,
|
|
76
118
|
});
|
|
77
|
-
switchRef = switchContainer;
|
|
78
119
|
|
|
79
120
|
// Handle click
|
|
80
121
|
if (!disabled) {
|