@solidrt/components 0.0.62 → 0.0.64
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/AGENTS.md +18 -14
- package/README.md +126 -21
- package/docs/badge.md +1 -1
- package/docs/checkbox.md +1 -1
- package/docs/context-menu.md +1 -1
- package/docs/item.md +1 -1
- package/docs/light.md +12 -0
- package/docs/motion.md +13 -0
- package/docs/press.md +19 -0
- package/docs/progress-bar.md +1 -1
- package/docs/radio.md +1 -1
- package/docs/segmented-control.md +1 -1
- package/docs/select.md +1 -1
- package/docs/slider.md +1 -1
- package/docs/surface.md +19 -0
- package/docs/switch.md +1 -1
- package/docs/theme.md +34 -6
- package/docs/tooltip.md +1 -1
- package/docs/types.md +1 -1
- package/docs/window.md +2 -0
- package/examples/lit.tsx +182 -0
- package/package.json +2 -2
- package/src/badge.tsx +25 -6
- package/src/button.tsx +44 -29
- package/src/card.tsx +76 -20
- package/src/checkbox.tsx +23 -19
- package/src/context-menu.tsx +21 -9
- package/src/editor-field.tsx +12 -7
- package/src/focus-nav.ts +17 -6
- package/src/glow.ts +35 -0
- package/src/index.ts +16 -1
- package/src/item.tsx +35 -13
- package/src/light.ts +274 -0
- package/src/motion.tsx +13 -1
- package/src/nav-shell.tsx +19 -10
- package/src/press.ts +2 -1
- package/src/pressable.tsx +6 -4
- package/src/progress-bar.tsx +25 -7
- package/src/radio.tsx +34 -22
- package/src/segmented-control.tsx +104 -23
- package/src/select.tsx +40 -34
- package/src/slider.tsx +74 -22
- package/src/surface.tsx +287 -0
- package/src/switch.tsx +48 -26
- package/src/theme.ts +201 -40
- package/src/tooltip.tsx +18 -14
- package/src/types.ts +66 -2
- package/src/window.tsx +26 -3
package/src/surface.tsx
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { Show, parseColor, withAlpha } from "@solidrt/core"
|
|
2
|
+
import type { Color, Gradient, ShadowProps, TransitionEndEvent, TransitionProps as CoreTransitionProps } from "@solidrt/core"
|
|
3
|
+
import { theme } from "./theme"
|
|
4
|
+
import { policy } from "./policy"
|
|
5
|
+
import { bevelStroke, contactShadow, fadedShadow, keyShadow, sheenFill, themeAxis } from "./light"
|
|
6
|
+
import { glowShadow } from "./glow"
|
|
7
|
+
import { colorFade, elevationMotion, feedbackFade } from "./motion"
|
|
8
|
+
import type { ElevationLevel, Glow, Material, MaterialRole } from "./types"
|
|
9
|
+
import { withTransitionDefaults } from "./types"
|
|
10
|
+
|
|
11
|
+
// The one way a component draws a face. A face is a role's material under
|
|
12
|
+
// the theme light at an elevation, and Surface stacks the layers that make
|
|
13
|
+
// it, in this order:
|
|
14
|
+
//
|
|
15
|
+
// contact shadow tight occlusion where the face meets the ground; stays
|
|
16
|
+
// when a press fades the key shadow out
|
|
17
|
+
// glow the material's emitted light, a zero-offset halo
|
|
18
|
+
// fill the base color, tinted by the light and with the sheen
|
|
19
|
+
// gradient along it when the material has one; carries
|
|
20
|
+
// the key shadow, which the elevation casts along the
|
|
21
|
+
// light, drops to a tenth while pressed and retargets to
|
|
22
|
+
// `liftTo` while lifted (all on the native shadow
|
|
23
|
+
// transition); under a glass material the fill sits in
|
|
24
|
+
// a backdrop-filtered view and the key shadow moves to a
|
|
25
|
+
// caster of its own
|
|
26
|
+
// tint the hover/pressed overlay (PressFeedback), only when
|
|
27
|
+
// the face is pressable (hovered or pressed given)
|
|
28
|
+
// bevel a one-pixel stroke from the lit edge to the shaded one
|
|
29
|
+
// outline the border or the focus ring the component decides on
|
|
30
|
+
//
|
|
31
|
+
// Everything is detached and fills the view Surface is rendered in, so it
|
|
32
|
+
// adds nothing to layout, except the glass view, which is absolutely
|
|
33
|
+
// positioned over the same box: render Surface as the FIRST child of the
|
|
34
|
+
// laid-out view it dresses. Under the stock presets (flat materials, zero
|
|
35
|
+
// elevation) a face is a plain fill, the tint and the outline: the look the
|
|
36
|
+
// package has always had.
|
|
37
|
+
//
|
|
38
|
+
// Shadows cast from opaque casters. The contact shadow and the glow sit on
|
|
39
|
+
// rects in the fill's own color, hidden under the fill; the key shadow on
|
|
40
|
+
// the fill rect itself. A transparent caster's blurred shadow composites in
|
|
41
|
+
// a save layer of its own, which a tiled GPU pays for per shadow - a list of
|
|
42
|
+
// such rows ran at 8-19 fps on every measured device
|
|
43
|
+
// (okf/notes/lit-list-cost.md) - so only a see-through face (glass, a
|
|
44
|
+
// translucent fill) casts from a transparent rect, and those are the few
|
|
45
|
+
// overlays.
|
|
46
|
+
|
|
47
|
+
export interface SurfaceProps {
|
|
48
|
+
role: MaterialRole
|
|
49
|
+
// The level the face stands at; default flat. The theme maps it to a
|
|
50
|
+
// height (theme.elevation), zero under the stock presets.
|
|
51
|
+
elevation?: ElevationLevel
|
|
52
|
+
// The base color: what the light tints and the sheen runs over, and the
|
|
53
|
+
// color a label's polarity is judged against. Defaults per role: surface
|
|
54
|
+
// and overlay to color.surface, control to surfaceAlt, accent to primary.
|
|
55
|
+
fill?: Color | Gradient
|
|
56
|
+
radius?: number | [number, number, number, number]
|
|
57
|
+
// Layers merged over the role's material (a component's theme override
|
|
58
|
+
// and instance style).
|
|
59
|
+
material?: Partial<Material>
|
|
60
|
+
// The glow layer alone: set replaces the material's, null clears it.
|
|
61
|
+
glow?: Glow | null
|
|
62
|
+
// A pressed face sinks: the key shadow fades toward the contact shadow and
|
|
63
|
+
// the lit edge moves to the far side. Giving `pressed` or `hovered` also
|
|
64
|
+
// mounts the tint layer, unless `tint` is false (a knob that sinks under
|
|
65
|
+
// the finger but takes no hover tint); `active` widens the glow on its
|
|
66
|
+
// own, for a face that neither tints nor sinks (a track under a press).
|
|
67
|
+
pressed?: boolean
|
|
68
|
+
hovered?: boolean
|
|
69
|
+
tint?: boolean
|
|
70
|
+
active?: boolean
|
|
71
|
+
// Hover lift: while `lifted`, the key shadow retargets to the one cast
|
|
72
|
+
// from `liftTo`. Without `liftTo` nothing lifts.
|
|
73
|
+
lifted?: boolean
|
|
74
|
+
liftTo?: ElevationLevel
|
|
75
|
+
// A well or a groove (a track, a field): shaded like a pressed face and
|
|
76
|
+
// casting nothing.
|
|
77
|
+
sunken?: boolean
|
|
78
|
+
// False hides fill, sheen, bevel, shadows and glow, keeping only the tint
|
|
79
|
+
// (a ghost button at rest). Default true.
|
|
80
|
+
chrome?: boolean
|
|
81
|
+
// The border or the focus ring, as the component resolves it; null or
|
|
82
|
+
// undefined draws none.
|
|
83
|
+
outline?: { color: Color | Gradient; width: number } | null
|
|
84
|
+
// The component's transition entries for its faces: `fillTransition` is
|
|
85
|
+
// its `background` entry (backgroundColor, borderRadius), `outlineTransition`
|
|
86
|
+
// its `border` entry, each already split from the component's `transition`
|
|
87
|
+
// prop; the fades and the elevation motion are filled in as defaults.
|
|
88
|
+
fillTransition?: CoreTransitionProps["transition"]
|
|
89
|
+
outlineTransition?: CoreTransitionProps["transition"]
|
|
90
|
+
onFillTransitionEnd?: (event: TransitionEndEvent) => void
|
|
91
|
+
onOutlineTransitionEnd?: (event: TransitionEndEvent) => void
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Key shadow strength while pressed: nearly gone, so the contact shadow is
|
|
95
|
+
// what remains of the face's lift.
|
|
96
|
+
const PRESSED_KEY_SHADOW = 0.1
|
|
97
|
+
// The saturation a glass face gives what shows through it: a little more
|
|
98
|
+
// than life, so the frosted backdrop reads as glass and not as fog.
|
|
99
|
+
const GLASS_SATURATE = 1.4
|
|
100
|
+
// The alpha of a glass tint that takes the face's own fill.
|
|
101
|
+
const GLASS_ALPHA = 0.6
|
|
102
|
+
// The bevel's stroke width: one logical pixel, an edge and not a band.
|
|
103
|
+
const BEVEL_WIDTH = 1
|
|
104
|
+
|
|
105
|
+
let isOpaque = (c: Color | Gradient) => typeof c !== "string" || ((parseColor(c) >>> 0) & 0xff) === 0xff
|
|
106
|
+
|
|
107
|
+
/** The fill a role's face takes when the component gives none. */
|
|
108
|
+
export function roleFill(role: MaterialRole): string {
|
|
109
|
+
switch (role) {
|
|
110
|
+
case "control":
|
|
111
|
+
return theme.color.surfaceAlt
|
|
112
|
+
case "accent":
|
|
113
|
+
return theme.color.primary
|
|
114
|
+
default:
|
|
115
|
+
return theme.color.surface
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The role's material with the component's layers merged over it. */
|
|
120
|
+
export function roleMaterial(role: MaterialRole, override?: Partial<Material>): Material {
|
|
121
|
+
let m = theme.material[role]
|
|
122
|
+
return override ? { ...m, ...override } : m
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Whether a face at this level gives a press something to sink: a sheen, a
|
|
127
|
+
* bevel or a height. A flat face keeps the press scale instead (see
|
|
128
|
+
* pressScale), since it has no depth to lose.
|
|
129
|
+
*/
|
|
130
|
+
export function surfaceSinks(role: MaterialRole, elevation: ElevationLevel = "flat", material?: Partial<Material>): boolean {
|
|
131
|
+
let m = roleMaterial(role, material)
|
|
132
|
+
return (m.sheen ?? 0) > 0 || (m.bevel ?? 0) > 0 || theme.elevation[elevation] > 0
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The paint of a face, for a part a component draws itself (a slider fill, a segmented indicator). */
|
|
136
|
+
export type FacePaint = {
|
|
137
|
+
fill: Color | Gradient
|
|
138
|
+
keyShadow?: ShadowProps
|
|
139
|
+
contactShadow?: ShadowProps
|
|
140
|
+
glow?: ShadowProps
|
|
141
|
+
bevel?: Gradient
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Derives a face's paint from the theme light and the role's material, the
|
|
146
|
+
* same derivation Surface draws with. `active` widens the glow (hovered or
|
|
147
|
+
* pressed); `pressed` and `sunken` invert the sheen and the bevel and, for a
|
|
148
|
+
* press, fade the key shadow. Reads the theme reactively: call it inside the
|
|
149
|
+
* prop expressions that use it.
|
|
150
|
+
*/
|
|
151
|
+
export function facePaint(o: {
|
|
152
|
+
role: MaterialRole
|
|
153
|
+
fill?: Color | Gradient
|
|
154
|
+
elevation?: ElevationLevel
|
|
155
|
+
material?: Partial<Material>
|
|
156
|
+
glow?: Glow | null
|
|
157
|
+
pressed?: boolean
|
|
158
|
+
sunken?: boolean
|
|
159
|
+
active?: boolean
|
|
160
|
+
}): FacePaint {
|
|
161
|
+
let light = theme.light
|
|
162
|
+
let axis = themeAxis()
|
|
163
|
+
let material = roleMaterial(o.role, o.material)
|
|
164
|
+
let base = o.fill ?? roleFill(o.role)
|
|
165
|
+
let invert = o.pressed === true || o.sunken === true
|
|
166
|
+
let height = o.sunken ? 0 : theme.elevation[o.elevation ?? "flat"]
|
|
167
|
+
let key = keyShadow(light, axis, height, theme.shadow)
|
|
168
|
+
return {
|
|
169
|
+
fill: sheenFill(base, light, axis, material.sheen, invert),
|
|
170
|
+
keyShadow: key && o.pressed ? fadedShadow(key, PRESSED_KEY_SHADOW) : key,
|
|
171
|
+
contactShadow: contactShadow(light, axis, height, theme.shadow),
|
|
172
|
+
glow: glowShadow(o.glow !== undefined ? o.glow : material.glow, base, o.active),
|
|
173
|
+
bevel: bevelStroke(light, axis, material.bevel, invert),
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function Surface(props: SurfaceProps) {
|
|
178
|
+
let chrome = () => props.chrome !== false
|
|
179
|
+
let base = () => props.fill ?? roleFill(props.role)
|
|
180
|
+
let material = () => roleMaterial(props.role, props.material)
|
|
181
|
+
let glass = () => (chrome() ? material().glass : undefined)
|
|
182
|
+
let paint = () =>
|
|
183
|
+
facePaint({
|
|
184
|
+
role: props.role,
|
|
185
|
+
fill: base(),
|
|
186
|
+
elevation: props.elevation,
|
|
187
|
+
material: props.material,
|
|
188
|
+
glow: props.glow,
|
|
189
|
+
pressed: props.pressed,
|
|
190
|
+
sunken: props.sunken,
|
|
191
|
+
active: props.active ?? (props.pressed || props.hovered),
|
|
192
|
+
})
|
|
193
|
+
// The key shadow the face casts now: while lifted, the one from the lift
|
|
194
|
+
// level; the retarget is what the shadow transition animates.
|
|
195
|
+
let key = (): ShadowProps | undefined => {
|
|
196
|
+
if (!chrome()) return undefined
|
|
197
|
+
if (props.lifted && props.liftTo && !props.sunken) {
|
|
198
|
+
let light = theme.light
|
|
199
|
+
return keyShadow(light, themeAxis(), theme.elevation[props.liftTo], theme.shadow)
|
|
200
|
+
}
|
|
201
|
+
return paint().keyShadow
|
|
202
|
+
}
|
|
203
|
+
let contact = () => (chrome() ? paint().contactShadow : undefined)
|
|
204
|
+
let glow = () => (chrome() ? paint().glow : undefined)
|
|
205
|
+
let fill = () => (chrome() ? paint().fill : "transparent")
|
|
206
|
+
let bevel = () => (chrome() ? paint().bevel : undefined)
|
|
207
|
+
// Opaque faces cast from casters in their own color, hidden under the
|
|
208
|
+
// fill; see-through ones from transparent casters (their own layer each).
|
|
209
|
+
let caster = () => (glass() || !isOpaque(base()) ? "transparent" : base())
|
|
210
|
+
let keyOnFill = () => !glass() && caster() !== "transparent"
|
|
211
|
+
let glassFill = () => {
|
|
212
|
+
let g = glass()!
|
|
213
|
+
let tint = g.tint ?? (typeof base() === "string" ? withAlpha(base() as string, GLASS_ALPHA) : base())
|
|
214
|
+
return sheenFill(tint, theme.light, themeAxis(), material().sheen, props.pressed === true || props.sunken === true)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let fillTransition = () => withTransitionDefaults(props.fillTransition, { ...colorFade(), ...elevationMotion() })
|
|
218
|
+
let shadowTransition = () => elevationMotion()
|
|
219
|
+
let outlineTransition = () => withTransitionDefaults(props.outlineTransition, colorFade())
|
|
220
|
+
let tinted = () => props.tint !== false && (props.pressed !== undefined || props.hovered !== undefined)
|
|
221
|
+
let tint = () => (props.pressed ? theme.color.overlayPressed : theme.color.overlayHover)
|
|
222
|
+
let tintFade = () => {
|
|
223
|
+
let f = feedbackFade()
|
|
224
|
+
return f === undefined ? undefined : { color: f }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<>
|
|
229
|
+
<Show when={contact()}>
|
|
230
|
+
{(shadow) => <d-rect color={caster()} radius={props.radius} shadow={shadow()} transition={shadowTransition()} />}
|
|
231
|
+
</Show>
|
|
232
|
+
<Show when={glow()}>{(shadow) => <d-rect color={caster()} radius={props.radius} shadow={shadow()} />}</Show>
|
|
233
|
+
<Show when={!keyOnFill() && key()}>
|
|
234
|
+
{(shadow) => <d-rect color="transparent" radius={props.radius} shadow={shadow()} transition={shadowTransition()} />}
|
|
235
|
+
</Show>
|
|
236
|
+
<Show
|
|
237
|
+
when={glass()}
|
|
238
|
+
fallback={
|
|
239
|
+
<d-rect
|
|
240
|
+
transition={fillTransition()}
|
|
241
|
+
onTransitionEnd={props.onFillTransitionEnd}
|
|
242
|
+
color={fill()}
|
|
243
|
+
radius={props.radius}
|
|
244
|
+
shadow={keyOnFill() ? key() : undefined}
|
|
245
|
+
/>
|
|
246
|
+
}
|
|
247
|
+
>
|
|
248
|
+
{(g) => (
|
|
249
|
+
<view
|
|
250
|
+
position="absolute"
|
|
251
|
+
top={0}
|
|
252
|
+
left={0}
|
|
253
|
+
right={0}
|
|
254
|
+
bottom={0}
|
|
255
|
+
overflow="hidden"
|
|
256
|
+
clipRadius={props.radius}
|
|
257
|
+
backdropFilter={{ blur: g().blur, saturate: GLASS_SATURATE }}
|
|
258
|
+
>
|
|
259
|
+
<d-rect transition={fillTransition()} onTransitionEnd={props.onFillTransitionEnd} color={glassFill()} radius={props.radius} />
|
|
260
|
+
</view>
|
|
261
|
+
)}
|
|
262
|
+
</Show>
|
|
263
|
+
<Show when={tinted()}>
|
|
264
|
+
<d-rect
|
|
265
|
+
transition={tintFade()}
|
|
266
|
+
color={props.pressed || props.hovered ? tint() : withAlpha(tint(), 0)}
|
|
267
|
+
radius={props.radius}
|
|
268
|
+
/>
|
|
269
|
+
</Show>
|
|
270
|
+
<Show when={bevel()}>
|
|
271
|
+
{(b) => <d-rect drawStyle="stroke" strokeWidth={BEVEL_WIDTH} color={b()} radius={props.radius} />}
|
|
272
|
+
</Show>
|
|
273
|
+
<Show when={props.outline}>
|
|
274
|
+
{(o) => (
|
|
275
|
+
<d-rect
|
|
276
|
+
drawStyle="stroke"
|
|
277
|
+
transition={outlineTransition()}
|
|
278
|
+
onTransitionEnd={props.onOutlineTransitionEnd}
|
|
279
|
+
color={o().color}
|
|
280
|
+
strokeWidth={o().width}
|
|
281
|
+
radius={props.radius}
|
|
282
|
+
/>
|
|
283
|
+
)}
|
|
284
|
+
</Show>
|
|
285
|
+
</>
|
|
286
|
+
)
|
|
287
|
+
}
|
package/src/switch.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { createSignal
|
|
1
|
+
import { createSignal } from "@solidrt/core"
|
|
2
2
|
import type { LayoutProps } from "@solidrt/core"
|
|
3
3
|
import { createPress } from "./press"
|
|
4
|
-
import { theme } from "./theme"
|
|
4
|
+
import { pill, theme } from "./theme"
|
|
5
5
|
import { policy } from "./policy"
|
|
6
6
|
import { densityScale } from "./density"
|
|
7
|
-
import
|
|
7
|
+
import { Surface, surfaceSinks } from "./surface"
|
|
8
|
+
import type { ElevationLevel, StyleProps, TransitionProps, TransitionStyleProp, TransitionViewProp } from "./types"
|
|
8
9
|
import { partTransition, partTransitionEnd, splitTransition, transitionEndFor, withTransitionDefaults } from "./types"
|
|
9
|
-
import {
|
|
10
|
+
import { pressScale, scaleFeedback, travelMotion } from "./motion"
|
|
10
11
|
|
|
11
12
|
export interface SwitchProps extends TransitionProps<TransitionViewProp | TransitionStyleProp | "knob"> {
|
|
12
13
|
// Controlled on/off. If omitted, the switch is uncontrolled.
|
|
@@ -25,11 +26,15 @@ const W = 44
|
|
|
25
26
|
const H = 24
|
|
26
27
|
const PAD = 2
|
|
27
28
|
|
|
28
|
-
// A toggle.
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
29
|
+
// A toggle. The track is a sunken face, accent when on (primary, with the
|
|
30
|
+
// accent material's glow), control when off (surfaceAlt), fading between
|
|
31
|
+
// them; the thumb (theme.color.thumb, else onPrimary) is a raised control
|
|
32
|
+
// face that springs across - the `knob` transition entry retimes it - and
|
|
33
|
+
// sinks under the finger where its material has depth. Track and thumb take
|
|
34
|
+
// pill(): round by default, square under radius.full 0. A press shrinks the
|
|
35
|
+
// control slightly (pressScale) while the thumb is flat; style.elevation
|
|
36
|
+
// sets the thumb's level. Controlled via value/onChange, or uncontrolled
|
|
37
|
+
// via defaultValue. When disabled, it takes no pointer events at all.
|
|
33
38
|
export function Switch(props: SwitchProps) {
|
|
34
39
|
let [internal, setInternal] = createSignal(props.defaultValue ?? false)
|
|
35
40
|
let on = () => (props.value !== undefined ? props.value : internal())
|
|
@@ -40,6 +45,9 @@ export function Switch(props: SwitchProps) {
|
|
|
40
45
|
props.onChange?.(next)
|
|
41
46
|
}
|
|
42
47
|
let press = createPress({ onPress: toggle })
|
|
48
|
+
// Widens the glow; hover counts only where a pointer can hover (the
|
|
49
|
+
// tint's rule).
|
|
50
|
+
let active = () => press.pressed() || (press.hovered() && policy.interaction !== "touch")
|
|
43
51
|
|
|
44
52
|
let w = () => Math.round(W * densityScale())
|
|
45
53
|
let h = () => Math.round(H * densityScale())
|
|
@@ -47,11 +55,13 @@ export function Switch(props: SwitchProps) {
|
|
|
47
55
|
|
|
48
56
|
let style = () => ({
|
|
49
57
|
backgroundColor: on() ? theme.color.primary : theme.color.surfaceAlt,
|
|
50
|
-
borderRadius: h()
|
|
58
|
+
borderRadius: pill(h()),
|
|
51
59
|
...theme.components.switch,
|
|
52
60
|
...props.style,
|
|
53
61
|
...(press.focused() && policy.focusRing ? { borderWidth: theme.borderWidth.focus, borderColor: theme.color.ring } : {}),
|
|
54
62
|
})
|
|
63
|
+
let thumbElevation = (): ElevationLevel => style().elevation ?? "raised"
|
|
64
|
+
let sinks = () => surfaceSinks("control", thumbElevation(), style().material)
|
|
55
65
|
|
|
56
66
|
let split = () => splitTransition(props.transition, ["knob"])
|
|
57
67
|
|
|
@@ -66,7 +76,7 @@ export function Switch(props: SwitchProps) {
|
|
|
66
76
|
{...props.layout}
|
|
67
77
|
x={style().x}
|
|
68
78
|
y={style().y}
|
|
69
|
-
scale={(style().scale ?? 1) * pressScale(press.pressed())}
|
|
79
|
+
scale={(style().scale ?? 1) * (sinks() ? 1 : pressScale(press.pressed()))}
|
|
70
80
|
rotate={style().rotate}
|
|
71
81
|
opacity={style().opacity}
|
|
72
82
|
{...press.handlers}
|
|
@@ -74,32 +84,44 @@ export function Switch(props: SwitchProps) {
|
|
|
74
84
|
focusable={!props.disabled}
|
|
75
85
|
pointerEvents={props.disabled ? "none" : undefined}
|
|
76
86
|
>
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
color={style().backgroundColor ?? "transparent"}
|
|
87
|
+
<Surface
|
|
88
|
+
role={on() ? "accent" : "control"}
|
|
89
|
+
fill={style().backgroundColor ?? "transparent"}
|
|
81
90
|
radius={style().borderRadius}
|
|
91
|
+
material={style().material}
|
|
92
|
+
glow={on() && !props.disabled ? style().glow : null}
|
|
93
|
+
sunken
|
|
94
|
+
active={active()}
|
|
95
|
+
outline={(style().borderWidth ?? 0) > 0 ? { color: style().borderColor ?? "transparent", width: style().borderWidth! } : null}
|
|
96
|
+
fillTransition={split().background}
|
|
97
|
+
onFillTransitionEnd={transitionEndFor("background", props.onTransitionEnd)}
|
|
98
|
+
outlineTransition={split().border}
|
|
99
|
+
onOutlineTransitionEnd={transitionEndFor("border", props.onTransitionEnd)}
|
|
82
100
|
/>
|
|
83
101
|
<view
|
|
84
102
|
position="absolute"
|
|
85
103
|
top={PAD}
|
|
86
104
|
left={PAD}
|
|
105
|
+
width={thumb()}
|
|
106
|
+
height={thumb()}
|
|
87
107
|
x={on() ? w() - thumb() - PAD * 2 : 0}
|
|
88
108
|
transition={partTransition(props.transition, "knob", "x", travelMotion())}
|
|
89
109
|
onTransitionEnd={partTransitionEnd("knob", "x", props.onTransitionEnd)}
|
|
90
110
|
>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
{/* The hairline keeps the thumb visible off: onPrimary on the
|
|
112
|
+
light scheme's surfaceAlt track is about 1.2:1 on its own. */}
|
|
113
|
+
<Surface
|
|
114
|
+
role="control"
|
|
115
|
+
elevation={thumbElevation()}
|
|
116
|
+
fill={theme.color.thumb ?? theme.color.onPrimary}
|
|
117
|
+
radius={pill(thumb())}
|
|
118
|
+
material={style().material}
|
|
119
|
+
glow={null}
|
|
120
|
+
pressed={press.pressed()}
|
|
121
|
+
tint={false}
|
|
122
|
+
outline={{ color: theme.color.border, width: theme.borderWidth.sm }}
|
|
101
123
|
/>
|
|
102
|
-
</
|
|
124
|
+
</view>
|
|
103
125
|
</view>
|
|
104
126
|
)
|
|
105
127
|
}
|