@lotics/ui 45.6.1 → 45.8.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 -4
- package/docs/catalog.md +23 -8
- package/docs/data_entry.md +23 -1
- package/package.json +1 -1
- package/src/brand_mark.tsx +35 -1
- package/src/inline_markdown.tsx +6 -0
- package/src/markdown_editor.css +40 -0
- package/src/markdown_editor.tsx +7 -1
- package/src/markdown_editor.web.tsx +2 -1
- package/src/markdown_editor_props.ts +37 -0
- package/src/switch_button.tsx +10 -2
package/AGENTS.md
CHANGED
|
@@ -104,10 +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
|
-
scanning a feed recognises the logo before the word. The
|
|
109
|
-
with no mark (a phone call, an event, a website) fails to
|
|
110
|
-
instead of borrowing another company's logo.
|
|
107
|
+
draws Facebook / Zalo / LinkedIn / X / Google Meet / Gmail / Outlook / Google Drive as solid
|
|
108
|
+
single-colour silhouettes — a reader scanning a feed recognises the logo before the word. The
|
|
109
|
+
names are an allowlist, so a channel with no mark (a phone call, an event, a website) fails to
|
|
110
|
+
typecheck and falls back to `Icon` instead of borrowing another company's logo. `SwitchButton`
|
|
111
|
+
takes one directly as `brand`, for a row naming an outside service.
|
|
112
|
+
→ [catalog.md §Text & formatting](./docs/catalog.md).
|
|
111
113
|
- **The kit's fonts/colors/icons ARE the design system** — never a custom font, icon set, or
|
|
112
114
|
hand-picked palette shade; color is `solid`/`tint`/`ramp` with ONE accent per screen.
|
|
113
115
|
- **Hand-typed type is off-system — and it always lands too small.** Every run of language is
|
package/docs/catalog.md
CHANGED
|
@@ -165,14 +165,22 @@ in `actions`),
|
|
|
165
165
|
the product's own knowledge docs use, not a raw textarea. It does NOT swap: the editor
|
|
166
166
|
is mounted at rest and merely gains a draft on focus, so focusing moves nothing and
|
|
167
167
|
the prose stays SELECTABLE rather than sitting under a button. Its toolbar is off for
|
|
168
|
-
that reason — a band that appears with the edit pushes everything under it down
|
|
168
|
+
that reason — a band that appears with the edit pushes everything under it down — and it
|
|
169
|
+
passes its **`variant`** THROUGH to the editor, which is what actually draws the box:
|
|
170
|
+
the frame paints no surface here, so a variant that stopped at it stopped at nothing and
|
|
171
|
+
every markdown field wore the document sheet, 4px right of the plain-text field above it.
|
|
169
172
|
**`numberOfLines`** is the reserve, as everywhere else in the family); a
|
|
170
173
|
READ-ONLY field in that same column uses `InlineStatic` (matches the editor box exactly, no
|
|
171
174
|
input chrome, so it aligns pixel-for-pixel). **`MarkdownEditor`** is that editor standalone — a WYSIWYG whose value stays a plain
|
|
172
175
|
markdown string, with a formatting toolbar as a SIBLING of the field (what the border
|
|
173
176
|
encloses is the document; the controls that act on it sit outside it). Web is
|
|
174
177
|
ProseMirror via `@lotics/markdown-editor`; native falls back to raw-text editing, and
|
|
175
|
-
the platform-neutral `MarkdownEditorProps` is what stops the two drifting.
|
|
178
|
+
the platform-neutral `MarkdownEditorProps` is what stops the two drifting. **`variant`**
|
|
179
|
+
is `"document"` (the default soft sheet) or the two FIELD states, `"framed"` / `"bare"`,
|
|
180
|
+
DERIVED from `InlineEditVariant` so they cannot drift from the rest of the family. A field
|
|
181
|
+
takes the control's corner and the control's text inset instead of the sheet's. The default
|
|
182
|
+
is NAMED rather than left as absence: an optional prop whose omission is a distinct third
|
|
183
|
+
mode reads exactly backwards to anyone who knows `TextInputField.variant`. Wrap it in a
|
|
176
184
|
label with **`FormMarkdownEditor`**. Pass **`accessibilityLabel`** whenever the visible
|
|
177
185
|
label is a sibling rather than a wrapping `FormField` — a `DetailRow`, an inline
|
|
178
186
|
editor's frame — or the editable surface is an unnamed text box. A stack of labelled
|
|
@@ -476,13 +484,17 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
476
484
|
`SubsectionHeading` instead. Pick between this and `Eyebrow` by what is LABELLED — a block or a
|
|
477
485
|
value; composition.md § Eyebrow vs group lead has the table.
|
|
478
486
|
- **`brand_mark`** — `BrandMark`: an OUTSIDE brand's mark, naming the channel a touchpoint
|
|
479
|
-
happened on — `facebook`, `zalo`, `linkedin`, `x`,
|
|
487
|
+
happened on or the service a connection reaches — `facebook`, `zalo`, `linkedin`, `x`,
|
|
488
|
+
`google-meet`, `gmail`, `outlook`, `google-drive`. Solid single-colour silhouettes in
|
|
480
489
|
each brand's own hex, not the full-colour logos: a byline draws these at 14–20px and a
|
|
481
490
|
five-colour logo turns to mud there. `size` (default 16) and `muted` (draw in the text colour).
|
|
482
|
-
The name list is an ALLOWLIST —
|
|
483
|
-
|
|
491
|
+
The name list is an ALLOWLIST — `BRAND_NAMES` is the array and `BrandName` derives from it, so a
|
|
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
|
|
484
494
|
own glyphs; that is `icon`. `x` is the one mark whose brand colour is BLACK, so it is the one
|
|
485
|
-
that needs `muted` on a dark ground; the rest carry their own hue either way.
|
|
495
|
+
that needs `muted` on a dark ground; the rest carry their own hue either way. Where a brand's
|
|
496
|
+
official mark is a composite (Outlook's envelope-and-O), the entry carries the half that stays
|
|
497
|
+
legible small — the identifying monogram — because a shape nobody can read is not the mark.
|
|
486
498
|
- **`markdown`** — `Markdown`: the single canonical markdown renderer for chat, apps, and
|
|
487
499
|
`AgentRun`; rich GFM markdown on web with copyable tables, plain-text on
|
|
488
500
|
native; takes a markdown `children` string. **`variant`** decides whose type
|
|
@@ -1059,9 +1071,12 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1059
1071
|
`DatePicker` outside a FormField must be given `accessibilityLabel` explicitly; range
|
|
1060
1072
|
formats suffix each half with the locale's start/end-date names.
|
|
1061
1073
|
- **`switch_button`** — `SwitchButton`: the full-ROW toggle — a `PressableHighlight` row
|
|
1062
|
-
(optional
|
|
1074
|
+
(optional leading mark + medium title left, `Switch` pinned right) where the whole row IS the
|
|
1063
1075
|
switch (`accessibilityRole="switch"`, the inner Switch read-only). The settings-panel/menu
|
|
1064
|
-
row toggle.
|
|
1076
|
+
row toggle. The leading slot takes EITHER `icon` (an `IconName`, the app's own glyph language)
|
|
1077
|
+
or `brand` (a `BrandName`, an outside service's mark); `icon` wins if both are passed. A row
|
|
1078
|
+
naming a third-party service takes `brand` — the two sets are deliberately separate, so a
|
|
1079
|
+
connector row cannot reach for a Lotics glyph to stand in for a company's logo.
|
|
1065
1080
|
- **`use_form`** — `useForm`: THE batch draft-form state hook — `values` = `initialValues` +
|
|
1066
1081
|
an edits overlay (a revalidation refreshes untouched fields, no sync effect), `validate`
|
|
1067
1082
|
(sync/async, gates submit, editing clears the field's error), `onSubmit(values, helpers)`
|
package/docs/data_entry.md
CHANGED
|
@@ -32,7 +32,11 @@ in [the templates](./templates.md) (`examples/tpl_*.tsx`).
|
|
|
32
32
|
**The right-input-per-field law.** A field gets the control its SHAPE wants, never a default
|
|
33
33
|
text box — and on a record surface radio/checkbox render as PERSISTENT controls (the control
|
|
34
34
|
is the best display of the value; prose-shaped values and registry picks stay inline
|
|
35
|
-
editors).
|
|
35
|
+
editors). **A MARKDOWN-typed field takes `InlineMarkdown`**, never `InlineTextInput`: the
|
|
36
|
+
plain input prints the syntax it stores, so a reader sees `**` where every other surface
|
|
37
|
+
showing that value renders it. It hides in the gap between two states of one field — the
|
|
38
|
+
value is formatted wherever it is read-only and raw only where it is EDITABLE, which is one
|
|
39
|
+
row, and looks like a quirk of that row rather than the wrong control. Rich `InlineSelect` options carry a description line (`renderOptionContent` — it
|
|
36
40
|
shows in the resting row too; the `data` generic types option payloads). A DEPENDENT field
|
|
37
41
|
renders only while its parent value makes it real — never a disabled ghost row. Worked rows:
|
|
38
42
|
`tpl_record`'s Classification group.
|
|
@@ -145,6 +149,24 @@ over the first. **When "the layout must not move" is a requirement rather than a
|
|
|
145
149
|
element is the only thing that delivers it** — a swap can be made to look stable, never to be it. So
|
|
146
150
|
the editor is mounted at rest and gains a draft on focus, and the per-field cost is paid on purpose.
|
|
147
151
|
If a screen ever holds dozens of these, the answer is fewer markdown fields on it, not a swap.
|
|
152
|
+
|
|
153
|
+
**`variant` has to reach whatever actually DRAWS the box.** `InlineEditFrame` paints no surface on
|
|
154
|
+
the path this field takes — it has no `actions` — so the editor's own frame is the field's frame,
|
|
155
|
+
and a `variant` that stops at the wrapper stops at nothing: `bare` bought no change at all, and
|
|
156
|
+
every markdown field wore the document sheet whatever the caller asked for. So `MarkdownEditor`
|
|
157
|
+
takes the same `"framed" | "bare"` axis as `TextInputField`, and `InlineMarkdown` passes it through.
|
|
158
|
+
|
|
159
|
+
**The half that is easy to miss is the INSET, not the border.** A document sheet insets its prose
|
|
160
|
+
further than a control does (13px against `CONTROL_TEXT_INSET`'s 9), so an editor serving as a
|
|
161
|
+
field lands 4px right of the plain-text field stacked directly above it — a ragged left edge inside
|
|
162
|
+
one card, with nothing on screen naming the cause. A field variant is therefore what makes the
|
|
163
|
+
editor a FIELD: it takes the control's corner (`CONTROL_RADIUS`) and the control's inset with it.
|
|
164
|
+
The third value, `"document"`, is the default and every standalone surface in the product — and it
|
|
165
|
+
is a NAMED value rather than an absence, because an optional prop whose omission is a distinct
|
|
166
|
+
third mode reads backwards to anyone who knows `TextInputField.variant`, where omitting it gives
|
|
167
|
+
you `"framed"`. `seamless` is the different case and not this one: that surrenders the surface to a
|
|
168
|
+
host that draws it, and here there is no host — `InlineEditFrame` paints one only when it also owns
|
|
169
|
+
the verbs, so with none the CHILD owns the surface.
|
|
148
170
|
**A LINK does not swap**: marking a URL changes its ink, not its characters, so `InlineTextInput`
|
|
149
171
|
takes a **`link`** treatment on its single `<input>` rather than swapping in a `TextLink`. Pressing
|
|
150
172
|
the field still edits — an `Open` `InlineButton` in `actions` is how you reach the destination,
|
package/package.json
CHANGED
package/src/brand_mark.tsx
CHANGED
|
@@ -7,7 +7,18 @@ 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
|
|
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;
|
|
20
|
+
|
|
21
|
+
export type BrandName = (typeof BRAND_NAMES)[number];
|
|
11
22
|
|
|
12
23
|
interface Mark {
|
|
13
24
|
/** The brand's own colour. A mark drawn in anything else is not the mark. */
|
|
@@ -52,6 +63,29 @@ const MARKS: Record<BrandName, Mark> = {
|
|
|
52
63
|
color: "#00897B",
|
|
53
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",
|
|
54
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
|
+
},
|
|
55
89
|
};
|
|
56
90
|
|
|
57
91
|
export interface BrandMarkProps {
|
package/src/inline_markdown.tsx
CHANGED
|
@@ -81,6 +81,12 @@ export function InlineMarkdown(props: InlineMarkdownProps) {
|
|
|
81
81
|
onFocus={edit.begin}
|
|
82
82
|
onBlur={() => void edit.commit()}
|
|
83
83
|
toolbar={false}
|
|
84
|
+
// THE FIELD'S FRAME IS THIS EDITOR'S OWN. `InlineEditFrame` draws no
|
|
85
|
+
// surface on the path this field takes (it has no `actions`), so the
|
|
86
|
+
// caller's `variant` has to reach the editor or it reaches nothing —
|
|
87
|
+
// which is what made a `bare` markdown field draw a document sheet
|
|
88
|
+
// anyway, 4px right of the plain-text field stacked above it.
|
|
89
|
+
variant={variant ?? "framed"}
|
|
84
90
|
placeholder={placeholder}
|
|
85
91
|
numberOfLines={numberOfLines}
|
|
86
92
|
disabled={disabled}
|
package/src/markdown_editor.css
CHANGED
|
@@ -37,6 +37,46 @@
|
|
|
37
37
|
opacity: 0.6;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/* THE TWO FIELD VARIANTS. Everything above is the `document` default and stays
|
|
41
|
+
the default: a standalone editor is a page's subject, and there are far more
|
|
42
|
+
of those than there are markdown FIELDS.
|
|
43
|
+
|
|
44
|
+
Both field variants are named explicitly rather than matched as "has a
|
|
45
|
+
variant at all". `document` is a real value that reaches the DOM too, so a
|
|
46
|
+
bare `[data-variant]` selector would catch the sheet and re-inset it — the
|
|
47
|
+
attribute says WHICH of three, never whether. */
|
|
48
|
+
.md-editor[data-variant="framed"],
|
|
49
|
+
.md-editor[data-variant="bare"] {
|
|
50
|
+
border-radius: 10px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.md-editor[data-variant="framed"] > .md-editor-content,
|
|
54
|
+
.md-editor[data-variant="bare"] > .md-editor-content {
|
|
55
|
+
padding-left: 8px;
|
|
56
|
+
padding-right: 8px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* The two numbers the field variants move are the KIT's, not this file's:
|
|
60
|
+
`CONTROL_RADIUS` (10) above replaces the sheet's 16 — a control corner, not a
|
|
61
|
+
page corner — and the text inset drops to `CONTROL_TEXT_INSET` (9) = a 1px
|
|
62
|
+
border plus 8px of padding, replacing the sheet's 13.
|
|
63
|
+
|
|
64
|
+
That second one is the whole visible defect this exists for: a document
|
|
65
|
+
insets its prose further than a control does, so an editor serving as a field
|
|
66
|
+
while keeping the sheet's inset lands 4px right of the plain-text field
|
|
67
|
+
stacked directly above it in the same card.
|
|
68
|
+
|
|
69
|
+
Vertical padding is untouched — a control's 40px height is governed by
|
|
70
|
+
`minHeight` elsewhere, and prose here is multi-line by definition.
|
|
71
|
+
|
|
72
|
+
BARE additionally has no ground and no edge at REST. The hover border and the
|
|
73
|
+
focus ring above still apply: a bare field draws its own frame, it just does
|
|
74
|
+
not draw it until you reach for it. */
|
|
75
|
+
.md-editor[data-variant="bare"] {
|
|
76
|
+
border-color: transparent;
|
|
77
|
+
background-color: transparent;
|
|
78
|
+
}
|
|
79
|
+
|
|
40
80
|
.md-editor-content {
|
|
41
81
|
flex: 1 1 auto;
|
|
42
82
|
/* position:relative anchors the absolutely-positioned gap cursor. */
|
package/src/markdown_editor.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import type { MarkdownEditorProps } from "./markdown_editor_props";
|
|
|
7
7
|
* edited as raw text.
|
|
8
8
|
*/
|
|
9
9
|
export function MarkdownEditor(props: MarkdownEditorProps) {
|
|
10
|
-
const { value, onChangeText, onBlur, onFocus, placeholder, numberOfLines, disabled, autoFocus, accessibilityLabel, testID } =
|
|
10
|
+
const { value, onChangeText, onBlur, onFocus, variant, placeholder, numberOfLines, disabled, autoFocus, accessibilityLabel, testID } =
|
|
11
11
|
props;
|
|
12
12
|
|
|
13
13
|
return (
|
|
@@ -19,6 +19,12 @@ export function MarkdownEditor(props: MarkdownEditorProps) {
|
|
|
19
19
|
onFocus={onFocus}
|
|
20
20
|
placeholder={placeholder}
|
|
21
21
|
numberOfLines={numberOfLines ?? 6}
|
|
22
|
+
// The two FIELD variants pass straight through — they are this input's
|
|
23
|
+
// own prop and mean the same thing here as on web. `"document"` has no
|
|
24
|
+
// native counterpart: the soft sheet is a web-only treatment and there is
|
|
25
|
+
// no raw-text equivalent of it, so a document editor falls back to this
|
|
26
|
+
// input's own default frame rather than inventing one.
|
|
27
|
+
variant={variant === "document" ? undefined : variant}
|
|
22
28
|
multiline
|
|
23
29
|
autoGrow
|
|
24
30
|
disabled={disabled}
|
|
@@ -15,7 +15,7 @@ import { MarkdownToolbar } from "./markdown_toolbar.web";
|
|
|
15
15
|
* plain markdown string. The ProseMirror engine lives in `@lotics/markdown-editor`.
|
|
16
16
|
*/
|
|
17
17
|
export function MarkdownEditor(props: MarkdownEditorProps) {
|
|
18
|
-
const { value, onChangeText, onBlur, onFocus, toolbar = true, placeholder, numberOfLines, disabled, autoFocus, accessibilityLabel, testID } =
|
|
18
|
+
const { value, onChangeText, onBlur, onFocus, toolbar = true, variant = "document", placeholder, numberOfLines, disabled, autoFocus, accessibilityLabel, testID } =
|
|
19
19
|
props;
|
|
20
20
|
|
|
21
21
|
const hostRef = useRef<HTMLDivElement>(null);
|
|
@@ -82,6 +82,7 @@ export function MarkdownEditor(props: MarkdownEditorProps) {
|
|
|
82
82
|
className="md-editor"
|
|
83
83
|
data-testid={testID}
|
|
84
84
|
data-disabled={disabled ? "true" : undefined}
|
|
85
|
+
data-variant={variant}
|
|
85
86
|
style={{ minHeight }}
|
|
86
87
|
/>
|
|
87
88
|
</>
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
import type { InlineEditVariant } from "./inline_edit";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What this editor IS, which decides the box it draws.
|
|
5
|
+
*
|
|
6
|
+
* DERIVED from `InlineEditVariant` rather than spelled out, so the two field
|
|
7
|
+
* states cannot drift from the rest of the `Inline*` family — this editor draws
|
|
8
|
+
* its own surface for the same reason every other no-verbs inline control does
|
|
9
|
+
* (`InlineEditFrame` paints one only when it also owns the ✓/✕ verbs), so it has
|
|
10
|
+
* to answer the same question in the same words.
|
|
11
|
+
*
|
|
12
|
+
* `"document"` is the third state and the DEFAULT, and it is named rather than
|
|
13
|
+
* left as absence on purpose: an optional two-value prop whose omission is a
|
|
14
|
+
* distinct third mode is a trap — nothing in the type says so, and the reader
|
|
15
|
+
* who knows `TextInputField.variant` (where omitting it means `"framed"`) reads
|
|
16
|
+
* it exactly backwards.
|
|
17
|
+
*/
|
|
18
|
+
export type MarkdownEditorVariant = "document" | InlineEditVariant;
|
|
19
|
+
|
|
1
20
|
/**
|
|
2
21
|
* Shared prop contract for the markdown editor. Declared platform-neutrally so
|
|
3
22
|
* the web (`markdown_editor.web.tsx`) and native (`markdown_editor.tsx`)
|
|
@@ -17,6 +36,24 @@ export interface MarkdownEditorProps {
|
|
|
17
36
|
* always there and shifts nothing, so it stays on.
|
|
18
37
|
*/
|
|
19
38
|
toolbar?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* `"document"` (the default) is a soft 16px sheet — a knowledge doc, a
|
|
41
|
+
* settings screen, anywhere the editor is the page's subject.
|
|
42
|
+
*
|
|
43
|
+
* `"framed"` and `"bare"` make it a FIELD, and mean there what they mean on
|
|
44
|
+
* every other inline control: an edge at rest, or no edge until hover. Both
|
|
45
|
+
* also pull the text inset in to `CONTROL_TEXT_INSET`, which is the half
|
|
46
|
+
* that is easy to miss — a document insets its prose further than a control
|
|
47
|
+
* does, so an editor serving as a field while keeping the sheet's inset sits
|
|
48
|
+
* 4px right of the plain-text field stacked above it.
|
|
49
|
+
*
|
|
50
|
+
* The editor draws this itself rather than taking it from a host, because
|
|
51
|
+
* `InlineEditFrame` paints a surface only when it also owns the ✓/✕ verbs;
|
|
52
|
+
* with none, the CHILD owns the surface. That is the same contract
|
|
53
|
+
* `TextInputField` answers with its own `variant`, and the reason `seamless`
|
|
54
|
+
* is the wrong lever here — nothing is drawing a box to surrender to.
|
|
55
|
+
*/
|
|
56
|
+
variant?: MarkdownEditorVariant;
|
|
20
57
|
placeholder?: string;
|
|
21
58
|
numberOfLines?: number;
|
|
22
59
|
disabled?: boolean;
|
package/src/switch_button.tsx
CHANGED
|
@@ -3,16 +3,24 @@ import { Text } from "./text";
|
|
|
3
3
|
import { Switch, type SwitchProps } from "./switch";
|
|
4
4
|
import { PressableHighlight } from "./pressable_highlight";
|
|
5
5
|
import { Icon, type IconName } from "./icon";
|
|
6
|
+
import { BrandMark, type BrandName } from "./brand_mark";
|
|
6
7
|
import { CONTROL_RADIUS } from "./control_surface";
|
|
7
8
|
|
|
8
9
|
export interface SwitchButtonProps extends SwitchProps {
|
|
9
10
|
icon?: IconName;
|
|
11
|
+
/**
|
|
12
|
+
* A third-party channel's own mark, for a row that names an OUTSIDE service.
|
|
13
|
+
* Separate from `icon` because the two draw from different sets on purpose —
|
|
14
|
+
* `Icon` is the app's own glyph language, `BrandMark` a narrow allowlist of
|
|
15
|
+
* other companies' logos. `icon` wins if both are passed.
|
|
16
|
+
*/
|
|
17
|
+
brand?: BrandName;
|
|
10
18
|
title: string;
|
|
11
19
|
tooltip?: string;
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
export function SwitchButton(props: SwitchButtonProps) {
|
|
15
|
-
const { title, icon, value, tooltip, onChange } = props;
|
|
23
|
+
const { title, icon, brand, value, tooltip, onChange } = props;
|
|
16
24
|
|
|
17
25
|
return (
|
|
18
26
|
<PressableHighlight
|
|
@@ -33,7 +41,7 @@ export function SwitchButton(props: SwitchButtonProps) {
|
|
|
33
41
|
aria-checked={!!value}
|
|
34
42
|
>
|
|
35
43
|
<View style={{ flexDirection: "row", gap: 8, alignItems: "center", flex: 1 }}>
|
|
36
|
-
{
|
|
44
|
+
{icon ? <Icon name={icon} size={20} /> : brand ? <BrandMark name={brand} size={20} /> : null}
|
|
37
45
|
{!!title && (
|
|
38
46
|
<Text weight="medium" userSelect="none">
|
|
39
47
|
{title}
|