@lotics/ui 45.8.0 → 45.9.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/AGENTS.md +6 -6
- package/MIGRATION.md +18 -0
- package/docs/catalog.md +23 -20
- package/package.json +1 -1
- package/src/back_button.tsx +79 -28
- package/src/brand_mark.tsx +17 -34
- package/src/switch_button.tsx +7 -8
package/AGENTS.md
CHANGED
|
@@ -104,12 +104,12 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
|
|
|
104
104
|
`error={overdue ? … }` is the tell: it makes a date due tomorrow read like one due in three
|
|
105
105
|
months. → [catalog.md §Dates & times](./docs/catalog.md).
|
|
106
106
|
- **A third-party channel takes `BrandMark`, our own glyphs take `Icon`.** `@lotics/ui/brand_mark`
|
|
107
|
-
draws Facebook / Zalo / LinkedIn / X / Google Meet
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
→ [catalog.md §Text & formatting](./docs/catalog.md).
|
|
107
|
+
draws Facebook / Zalo / LinkedIn / X / Google Meet as solid single-colour silhouettes — a reader
|
|
108
|
+
scanning a feed recognises the logo before the word. The names are an allowlist, so a channel with
|
|
109
|
+
no mark (a phone call, an event, a website) fails to typecheck and falls back to `Icon` instead of
|
|
110
|
+
borrowing another company's logo. A product's official full-colour logo is NOT one of these: the
|
|
111
|
+
kit ships no vendor artwork, so a row that names a product passes its own image into
|
|
112
|
+
`SwitchButton`'s `leading` slot. → [catalog.md §Text & formatting](./docs/catalog.md).
|
|
113
113
|
- **The kit's fonts/colors/icons ARE the design system** — never a custom font, icon set, or
|
|
114
114
|
hand-picked palette shade; color is `solid`/`tint`/`ramp` with ONE accent per screen.
|
|
115
115
|
- **Hand-typed type is off-system — and it always lands too small.** Every run of language is
|
package/MIGRATION.md
CHANGED
|
@@ -4,6 +4,24 @@ Breaking changes, newest first — normally per major, plus the rare minor that
|
|
|
4
4
|
anyway (recorded under its exact version). The current contract lives in `AGENTS.md` + `docs/`;
|
|
5
5
|
this file exists only to move an app from one release to the next.
|
|
6
6
|
|
|
7
|
+
## 45.9.0
|
|
8
|
+
|
|
9
|
+
**`BrandMark` no longer carries product logos, and `SwitchButton` takes `leading` instead of
|
|
10
|
+
`brand`.** `BRAND_NAMES` drops `gmail`, `outlook` and `google-drive`; the kit's marks are the
|
|
11
|
+
single-colour CC0 silhouettes that NAME a channel — `facebook`, `zalo`, `linkedin`, `x`,
|
|
12
|
+
`google-meet` — and a monochrome stand-in for a product logo reads as a different thing.
|
|
13
|
+
|
|
14
|
+
Replace `<BrandMark name="gmail" />` with your own image asset. A row that named a product with
|
|
15
|
+
`<SwitchButton brand="gmail" …>` passes it through the new `leading` slot instead, which takes any
|
|
16
|
+
node:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<SwitchButton leading={<Image source={{ uri: logo }} style={{ width: 20, height: 20 }} />} … />
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A retired name no longer typechecks, and a bundle compiled against an older kit renders nothing
|
|
23
|
+
where the mark was rather than throwing.
|
|
24
|
+
|
|
7
25
|
## 45.5.0
|
|
8
26
|
|
|
9
27
|
**`InlineSelect` and `InlineMemberSelect` are a `combobox`, not a `button`.** They rest as a
|
package/docs/catalog.md
CHANGED
|
@@ -484,17 +484,16 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
484
484
|
`SubsectionHeading` instead. Pick between this and `Eyebrow` by what is LABELLED — a block or a
|
|
485
485
|
value; composition.md § Eyebrow vs group lead has the table.
|
|
486
486
|
- **`brand_mark`** — `BrandMark`: an OUTSIDE brand's mark, naming the channel a touchpoint
|
|
487
|
-
happened on
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
channel with no mark will not typecheck and the call site falls back to a generic `Icon` rather
|
|
493
|
-
than rendering the wrong company's logo. Not for the app's
|
|
487
|
+
happened on — `facebook`, `zalo`, `linkedin`, `x`, `google-meet`. Solid single-colour silhouettes
|
|
488
|
+
in each brand's own hex, drawn from CC0 geometry. `size` (default 16) and `muted` (draw in the
|
|
489
|
+
text colour). The name list is an ALLOWLIST — `BRAND_NAMES` is the array and `BrandName` derives
|
|
490
|
+
from it, so a channel with no mark will not typecheck and the call site falls back to a generic
|
|
491
|
+
`Icon` rather than rendering the wrong company's logo. Not for the app's
|
|
494
492
|
own glyphs; that is `icon`. `x` is the one mark whose brand colour is BLACK, so it is the one
|
|
495
|
-
that needs `muted` on a dark ground; the rest carry their own hue either way.
|
|
496
|
-
|
|
497
|
-
|
|
493
|
+
that needs `muted` on a dark ground; the rest carry their own hue either way. **Not a product
|
|
494
|
+
logo**: a monochrome stand-in for one reads as a different thing (a red "M" is not Gmail), the kit
|
|
495
|
+
ships no vendor artwork, and official artwork is not uniformly inlinable anyway — pass your own
|
|
496
|
+
image to `SwitchButton`'s `leading` slot instead.
|
|
498
497
|
- **`markdown`** — `Markdown`: the single canonical markdown renderer for chat, apps, and
|
|
499
498
|
`AgentRun`; rich GFM markdown on web with copyable tables, plain-text on
|
|
500
499
|
native; takes a markdown `children` string. **`variant`** decides whose type
|
|
@@ -835,13 +834,18 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
835
834
|
rather than a value). Throws when the clipboard is unreachable — never resolve a write
|
|
836
835
|
that did not happen, or the caller confirms a lie. Web-only (native has no
|
|
837
836
|
`navigator.clipboard`).
|
|
838
|
-
- **`back_button`** — `BackButton`: the
|
|
839
|
-
`arrow-left` `MenuButton` reads as another nav ITEM, not
|
|
840
|
-
`accessibilityLabel` (defaults to `label`, else the locale's
|
|
841
|
-
"Quay lại"). Bare
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
837
|
+
- **`back_button`** — `BackButton`: the CIRCULAR go-back control that HEADS a screen or
|
|
838
|
+
panel — don't hand-roll it (an `arrow-left` `MenuButton` reads as another nav ITEM, not
|
|
839
|
+
an exit). `onPress` + `accessibilityLabel` (defaults to `label`, else the locale's
|
|
840
|
+
`nav.back` — "Back" / "Quay lại"). Bare it is the 40px chevron-left disc; pass
|
|
841
|
+
**`label`** to name where back GOES ("Danh sách") — the SAME disc with the destination
|
|
842
|
+
beside it, ONE pressable over disc + words, never an icon button beside unclickable
|
|
843
|
+
prose. It takes no size or colour: one purpose, one look, and both variants draw the
|
|
844
|
+
one disc so they cannot drift.
|
|
845
|
+
**A back affordance INSIDE a header row is not this** — beside a drawer's title a 40px
|
|
846
|
+
disc outweighs the name it belongs to, so that one is a small `IconButton` (see
|
|
847
|
+
`screen_router` below). The distinction is prominence: this HEADS a surface, that
|
|
848
|
+
annotates a title.
|
|
845
849
|
- **`link`** — `Link`: the EXTERNAL hyperlink — fixed underline+blue + `role="link"`;
|
|
846
850
|
`onPress` only (the consumer wires the opener).
|
|
847
851
|
- **`text_link`** — `TextLink`: underlined text that NAVIGATES — `href` renders a real
|
|
@@ -1074,9 +1078,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1074
1078
|
(optional leading mark + medium title left, `Switch` pinned right) where the whole row IS the
|
|
1075
1079
|
switch (`accessibilityRole="switch"`, the inner Switch read-only). The settings-panel/menu
|
|
1076
1080
|
row toggle. The leading slot takes EITHER `icon` (an `IconName`, the app's own glyph language)
|
|
1077
|
-
or `
|
|
1078
|
-
|
|
1079
|
-
connector row cannot reach for a Lotics glyph to stand in for a company's logo.
|
|
1081
|
+
or `leading` (any node); `icon` wins if both are passed. A row naming an outside product passes
|
|
1082
|
+
its logo through `leading`, because the kit ships no vendor artwork of its own.
|
|
1080
1083
|
- **`use_form`** — `useForm`: THE batch draft-form state hook — `values` = `initialValues` +
|
|
1081
1084
|
an edits overlay (a revalidation refreshes untouched fields, no sync effect), `validate`
|
|
1082
1085
|
(sync/async, gates submit, editing clears the field's error), `onSubmit(values, helpers)`
|
package/package.json
CHANGED
package/src/back_button.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { IconButton } from "./icon_button";
|
|
2
1
|
import { View } from "react-native";
|
|
3
2
|
import { Icon } from "./icon";
|
|
4
3
|
import { Text } from "./text";
|
|
5
4
|
import { colors } from "./colors";
|
|
6
|
-
import { CURSOR_ACTION } from "./control_surface";
|
|
5
|
+
import { CONTROL_HEIGHT, CURSOR_ACTION } from "./control_surface";
|
|
7
6
|
import { PressableHighlight } from "./pressable_highlight";
|
|
8
7
|
import { useLoticsLocale } from "./locale";
|
|
9
8
|
|
|
@@ -18,26 +17,66 @@ interface BackButtonProps {
|
|
|
18
17
|
accessibilityLabel?: string;
|
|
19
18
|
}
|
|
20
19
|
|
|
20
|
+
/**
|
|
21
|
+
* THE DISC, drawn ONCE for both variants and pressable in neither.
|
|
22
|
+
*
|
|
23
|
+
* It used to exist twice: the bare variant delegated to an `IconButton`, the
|
|
24
|
+
* labelled one hand-rolled the identical circle and restated that component's
|
|
25
|
+
* spec in a comment — "mirrors IconButton lg/secondary (40px, zinc-100, 20px
|
|
26
|
+
* zinc-900 glyph)". A spec copied into a comment is a spec that drifts: change
|
|
27
|
+
* `secondary` and one variant of ONE component follows while the other does not.
|
|
28
|
+
*
|
|
29
|
+
* Delegating both to `IconButton` is not the fix and never was. The labelled
|
|
30
|
+
* form is ONE pressable over the disc AND the words — an `IconButton` inside it
|
|
31
|
+
* is a button nested in a button, which is why the copy got written in the first
|
|
32
|
+
* place. So the shared thing is the SURFACE, not the control: inert here, and
|
|
33
|
+
* whichever pressable wraps it owns the gesture.
|
|
34
|
+
*
|
|
35
|
+
* IT BORROWS NOTHING FROM ANOTHER CONTROL. An earlier pass took the glyph ink
|
|
36
|
+
* from `getButtonIconColor("secondary")`, which reads as sharing until you say it
|
|
37
|
+
* out loud: this is not a secondary BUTTON, and tying its ink to that ladder
|
|
38
|
+
* means a change to the button palette silently restyles the back affordance for
|
|
39
|
+
* a reason no one would connect. The ink is `zinc-900` because that is what this
|
|
40
|
+
* glyph is.
|
|
41
|
+
*
|
|
42
|
+
* `CONTROL_HEIGHT` stays, and is not the same kind of borrowing: it is the
|
|
43
|
+
* SYSTEM's band height — the promise every 40px control aligns to — not another
|
|
44
|
+
* component's opinion. Typing `40` here would be the drift, not the reuse.
|
|
45
|
+
*/
|
|
46
|
+
const GLYPH = 20;
|
|
47
|
+
|
|
48
|
+
function Disc() {
|
|
49
|
+
return (
|
|
50
|
+
<View
|
|
51
|
+
style={{
|
|
52
|
+
width: CONTROL_HEIGHT,
|
|
53
|
+
height: CONTROL_HEIGHT,
|
|
54
|
+
borderRadius: 999,
|
|
55
|
+
backgroundColor: colors.zinc[100],
|
|
56
|
+
alignItems: "center",
|
|
57
|
+
justifyContent: "center",
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<Icon name="chevron-left" size={GLYPH} color={colors.zinc[900]} />
|
|
61
|
+
</View>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* THE go-back control: a circular glyph that leaves the current screen or panel,
|
|
67
|
+
* optionally naming where it goes.
|
|
68
|
+
*
|
|
69
|
+
* It is one purpose, so it takes no size or colour — a caller reaching for those
|
|
70
|
+
* wants a different control. In particular a drawer header's small inline arrow
|
|
71
|
+
* is NOT this: that sits beside a title inside a header row, where a 40px disc
|
|
72
|
+
* would outweigh the name it belongs to. That one is an `IconButton`, and the
|
|
73
|
+
* distinction is prominence — this one HEADS a surface, that one annotates a
|
|
74
|
+
* title.
|
|
75
|
+
*/
|
|
21
76
|
export function BackButton(props: BackButtonProps) {
|
|
22
77
|
const locale = useLoticsLocale();
|
|
23
78
|
const { onPress, label, accessibilityLabel = label ?? locale.nav.back } = props;
|
|
24
|
-
|
|
25
|
-
if (label === undefined) {
|
|
26
|
-
return (
|
|
27
|
-
<IconButton
|
|
28
|
-
icon="chevron-left"
|
|
29
|
-
size="lg"
|
|
30
|
-
color="secondary"
|
|
31
|
-
accessibilityLabel={accessibilityLabel}
|
|
32
|
-
onPress={onPress}
|
|
33
|
-
style={{ alignSelf: "flex-start" }}
|
|
34
|
-
/>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
// Labelled — the SAME circular glyph, the destination named beside it, ONE
|
|
38
|
-
// pressable pill over both (words you can't press are a lie). The disc
|
|
39
|
-
// mirrors IconButton lg/secondary (40px, zinc-100, 20px zinc-900 glyph); the
|
|
40
|
-
// pill takes PressableHighlight's default hover/press wash + focus ring.
|
|
79
|
+
|
|
41
80
|
return (
|
|
42
81
|
<PressableHighlight
|
|
43
82
|
focusRing
|
|
@@ -45,16 +84,28 @@ export function BackButton(props: BackButtonProps) {
|
|
|
45
84
|
accessibilityLabel={accessibilityLabel}
|
|
46
85
|
userSelect="none"
|
|
47
86
|
onPress={onPress}
|
|
48
|
-
// Going back ACTS
|
|
49
|
-
//
|
|
50
|
-
|
|
87
|
+
// Going back ACTS. One cursor for both variants — a label must not decide
|
|
88
|
+
// whether the same control looks pressable.
|
|
89
|
+
//
|
|
90
|
+
// The pill's own padding exists only when there are words to seat: bare,
|
|
91
|
+
// the disc IS the target and any padding would inflate it past the 40px
|
|
92
|
+
// every other control in the band is aligned to.
|
|
93
|
+
style={{
|
|
94
|
+
cursor: CURSOR_ACTION,
|
|
95
|
+
flexDirection: "row",
|
|
96
|
+
alignItems: "center",
|
|
97
|
+
gap: 8,
|
|
98
|
+
alignSelf: "flex-start",
|
|
99
|
+
borderRadius: 999,
|
|
100
|
+
paddingRight: label === undefined ? 0 : 14,
|
|
101
|
+
}}
|
|
51
102
|
>
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
103
|
+
<Disc />
|
|
104
|
+
{label === undefined ? null : (
|
|
105
|
+
<Text size="sm" weight="medium" color="muted" numberOfLines={1}>
|
|
106
|
+
{label}
|
|
107
|
+
</Text>
|
|
108
|
+
)}
|
|
58
109
|
</PressableHighlight>
|
|
59
110
|
);
|
|
60
111
|
}
|
package/src/brand_mark.tsx
CHANGED
|
@@ -7,16 +7,7 @@ import { Svg, Path } from "react-native-svg";
|
|
|
7
7
|
* email that is not tied to one provider. Inventing a mark for those is how a
|
|
8
8
|
* byline ends up claiming a channel the record does not carry.
|
|
9
9
|
*/
|
|
10
|
-
export const BRAND_NAMES = [
|
|
11
|
-
"facebook",
|
|
12
|
-
"zalo",
|
|
13
|
-
"linkedin",
|
|
14
|
-
"x",
|
|
15
|
-
"google-meet",
|
|
16
|
-
"gmail",
|
|
17
|
-
"outlook",
|
|
18
|
-
"google-drive",
|
|
19
|
-
] as const;
|
|
10
|
+
export const BRAND_NAMES = ["facebook", "zalo", "linkedin", "x", "google-meet"] as const;
|
|
20
11
|
|
|
21
12
|
export type BrandName = (typeof BRAND_NAMES)[number];
|
|
22
13
|
|
|
@@ -37,6 +28,15 @@ interface Mark {
|
|
|
37
28
|
* Path data is Simple Icons (CC0), so the geometry carries no licence of its
|
|
38
29
|
* own; the marks themselves stay the trademarks of their owners and are used
|
|
39
30
|
* here only to NAME the channel a touchpoint happened on.
|
|
31
|
+
*
|
|
32
|
+
* A product's OFFICIAL full-colour logo does not belong here, and the reason is
|
|
33
|
+
* not size — at 20px the real Gmail and Drive artwork reads perfectly well. It
|
|
34
|
+
* is provenance and shape: official artwork is the vendor's asset rather than
|
|
35
|
+
* CC0 geometry, and it is not uniformly inlinable — Gmail and Drive are flat
|
|
36
|
+
* multi-path files, Outlook is fourteen gradients across fifteen paths. A kit
|
|
37
|
+
* that carried two of them and gave up on the third would be the worst of both.
|
|
38
|
+
* Those live in the app's own image assets, and a row that wants one passes it
|
|
39
|
+
* in (`SwitchButton`'s `leading` slot).
|
|
40
40
|
*/
|
|
41
41
|
const MARKS: Record<BrandName, Mark> = {
|
|
42
42
|
facebook: {
|
|
@@ -63,29 +63,6 @@ const MARKS: Record<BrandName, Mark> = {
|
|
|
63
63
|
color: "#00897B",
|
|
64
64
|
path: "M5.53 2.13 0 7.75h5.53zm.398 0v5.62h7.608v3.65l5.47-4.45c-.014-1.22.031-2.25-.025-3.46-.148-1.09-1.287-1.47-2.236-1.36zM23.1 4.32c-.802.295-1.358.995-2.047 1.49-2.506 2.05-4.982 4.12-7.468 6.19 3.025 2.59 6.04 5.18 9.065 7.76 1.218.671 1.428-.814 1.328-1.64v-13a.828.828 0 0 0-.877-.825zM.038 8.15v7.7h5.53v-7.7zm13.577 8.1H6.008v5.62c3.864-.006 7.737.011 11.58-.009 1.02-.07 1.618-1.12 1.468-2.07v-2.51l-5.47-4.68v3.65zm-13.577 0c.02 1.44-.041 2.88.033 4.31.162.948 1.158 1.43 2.047 1.31h3.464v-5.62z",
|
|
65
65
|
},
|
|
66
|
-
// The envelope silhouette, in Gmail's red. The full mark is five colours
|
|
67
|
-
// meeting along the flap's diagonals — at 16px those seams alias into a
|
|
68
|
-
// brown smear, which is precisely the case the note above is about.
|
|
69
|
-
gmail: {
|
|
70
|
-
color: "#EA4335",
|
|
71
|
-
path: "M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64 12 9.548l6.545-4.909 1.528-1.146C21.69 2.28 24 3.434 24 5.457z",
|
|
72
|
-
},
|
|
73
|
-
// The bare "O" monogram — an oval ring, drawn as two subpaths of OPPOSITE
|
|
74
|
-
// winding so the nonzero fill leaves the counter open. Simple Icons' Outlook
|
|
75
|
-
// path is the envelope-and-O composite, and it was tried here first: at 20px
|
|
76
|
-
// it collapses into a blue rectangle with no readable feature, and it is not
|
|
77
|
-
// much better at 64. The O alone is what identifies the brand at a glance and
|
|
78
|
-
// it is the half that survives the size.
|
|
79
|
-
outlook: {
|
|
80
|
-
color: "#0078D4",
|
|
81
|
-
path: "M12 1.2a8.7 10.8 0 1 0 0 21.6 8.7 10.8 0 1 0 0-21.6ZM12 6.6a4.1 5.4 0 1 1 0 10.8 4.1 5.4 0 1 1 0-10.8Z",
|
|
82
|
-
},
|
|
83
|
-
// Drive's triangle, drawn as one silhouette. The three-colour original is the
|
|
84
|
-
// same aliasing problem as Gmail's, and worse — its colours meet at a point.
|
|
85
|
-
"google-drive": {
|
|
86
|
-
color: "#1FA463",
|
|
87
|
-
path: "M12.01 1.485c-2.082 0-3.754.02-3.743.047.011.024 1.708 3.001 3.774 6.62l3.76 6.574h7.46c.001-.02-1.696-2.998-3.762-6.618l-3.76-6.623zm-4.28.939a5.482 5.482 0 0 0-.309.443L3.71 9.17 0 15.647l1.863 3.209a338.99 338.99 0 0 0 1.914 3.28c.04.045 7.55-13.116 7.55-13.234 0-.045-1.487-2.652-3.305-5.793zM9.97 15.463c-.639 1.099-1.943 3.361-2.9 5.03l-1.737 3.021h15.09c0-.019.848-1.484 1.884-3.259l1.882-3.222H9.97z",
|
|
88
|
-
},
|
|
89
66
|
};
|
|
90
67
|
|
|
91
68
|
export interface BrandMarkProps {
|
|
@@ -112,7 +89,13 @@ export interface BrandMarkProps {
|
|
|
112
89
|
*/
|
|
113
90
|
export function BrandMark(props: BrandMarkProps) {
|
|
114
91
|
const { name, size = 16, muted, testID } = props;
|
|
115
|
-
|
|
92
|
+
// A name outside the allowlist does not typecheck, so reaching here means a
|
|
93
|
+
// deployed app compiled against an older kit that still had the name. Render
|
|
94
|
+
// nothing rather than dereferencing undefined: a mark is decoration beside a
|
|
95
|
+
// label that already names the channel, and throwing takes the whole render
|
|
96
|
+
// tree of an app that is otherwise fine.
|
|
97
|
+
const mark = MARKS[name] as Mark | undefined;
|
|
98
|
+
if (!mark) return null;
|
|
116
99
|
return (
|
|
117
100
|
<Svg width={size} height={size} viewBox="0 0 24 24" testID={testID}>
|
|
118
101
|
<Path d={mark.path} fill={muted ? "currentColor" : mark.color} />
|
package/src/switch_button.tsx
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
1
2
|
import { View } from "react-native";
|
|
2
3
|
import { Text } from "./text";
|
|
3
4
|
import { Switch, type SwitchProps } from "./switch";
|
|
4
5
|
import { PressableHighlight } from "./pressable_highlight";
|
|
5
6
|
import { Icon, type IconName } from "./icon";
|
|
6
|
-
import { BrandMark, type BrandName } from "./brand_mark";
|
|
7
7
|
import { CONTROL_RADIUS } from "./control_surface";
|
|
8
8
|
|
|
9
9
|
export interface SwitchButtonProps extends SwitchProps {
|
|
10
10
|
icon?: IconName;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* other companies' logos. `icon` wins if both are passed.
|
|
12
|
+
* Anything to draw in the leading slot instead of an `Icon` — a product logo
|
|
13
|
+
* a row names, which the caller owns because the kit does not ship other
|
|
14
|
+
* companies' artwork. `icon` wins if both are passed.
|
|
16
15
|
*/
|
|
17
|
-
|
|
16
|
+
leading?: ReactNode;
|
|
18
17
|
title: string;
|
|
19
18
|
tooltip?: string;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
export function SwitchButton(props: SwitchButtonProps) {
|
|
23
|
-
const { title, icon,
|
|
22
|
+
const { title, icon, leading, value, tooltip, onChange } = props;
|
|
24
23
|
|
|
25
24
|
return (
|
|
26
25
|
<PressableHighlight
|
|
@@ -41,7 +40,7 @@ export function SwitchButton(props: SwitchButtonProps) {
|
|
|
41
40
|
aria-checked={!!value}
|
|
42
41
|
>
|
|
43
42
|
<View style={{ flexDirection: "row", gap: 8, alignItems: "center", flex: 1 }}>
|
|
44
|
-
{icon ? <Icon name={icon} size={20} /> :
|
|
43
|
+
{icon ? <Icon name={icon} size={20} /> : (leading ?? null)}
|
|
45
44
|
{!!title && (
|
|
46
45
|
<Text weight="medium" userSelect="none">
|
|
47
46
|
{title}
|