@lotics/ui 45.6.1 → 45.7.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 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 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
109
- with no mark (a phone call, an event, a website) fails to typecheck and falls back to `Icon`
110
- instead of borrowing another company's logo. → [catalog.md §Text & formatting](./docs/catalog.md).
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
@@ -476,13 +476,17 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
476
476
  `SubsectionHeading` instead. Pick between this and `Eyebrow` by what is LABELLED — a block or a
477
477
  value; composition.md § Eyebrow vs group lead has the table.
478
478
  - **`brand_mark`** — `BrandMark`: an OUTSIDE brand's mark, naming the channel a touchpoint
479
- happened on — `facebook`, `zalo`, `linkedin`, `x`, `google-meet`. Solid single-colour silhouettes in
479
+ happened on or the service a connection reaches — `facebook`, `zalo`, `linkedin`, `x`,
480
+ `google-meet`, `gmail`, `outlook`, `google-drive`. Solid single-colour silhouettes in
480
481
  each brand's own hex, not the full-colour logos: a byline draws these at 14–20px and a
481
482
  five-colour logo turns to mud there. `size` (default 16) and `muted` (draw in the text colour).
482
- The name list is an ALLOWLIST — a channel with no mark will not typecheck, so the call site
483
- falls back to a generic `Icon` rather than rendering the wrong company's logo. Not for the app's
483
+ The name list is an ALLOWLIST — `BRAND_NAMES` is the array and `BrandName` derives from it, so a
484
+ channel with no mark will not typecheck and the call site falls back to a generic `Icon` rather
485
+ than rendering the wrong company's logo. Not for the app's
484
486
  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.
487
+ that needs `muted` on a dark ground; the rest carry their own hue either way. Where a brand's
488
+ official mark is a composite (Outlook's envelope-and-O), the entry carries the half that stays
489
+ legible small — the identifying monogram — because a shape nobody can read is not the mark.
486
490
  - **`markdown`** — `Markdown`: the single canonical markdown renderer for chat, apps, and
487
491
  `AgentRun`; rich GFM markdown on web with copyable tables, plain-text on
488
492
  native; takes a markdown `children` string. **`variant`** decides whose type
@@ -1059,9 +1063,12 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
1059
1063
  `DatePicker` outside a FormField must be given `accessibilityLabel` explicitly; range
1060
1064
  formats suffix each half with the locale's start/end-date names.
1061
1065
  - **`switch_button`** — `SwitchButton`: the full-ROW toggle — a `PressableHighlight` row
1062
- (optional icon + medium title left, `Switch` pinned right) where the whole row IS the
1066
+ (optional leading mark + medium title left, `Switch` pinned right) where the whole row IS the
1063
1067
  switch (`accessibilityRole="switch"`, the inner Switch read-only). The settings-panel/menu
1064
- row toggle.
1068
+ row toggle. The leading slot takes EITHER `icon` (an `IconName`, the app's own glyph language)
1069
+ or `brand` (a `BrandName`, an outside service's mark); `icon` wins if both are passed. A row
1070
+ naming a third-party service takes `brand` — the two sets are deliberately separate, so a
1071
+ connector row cannot reach for a Lotics glyph to stand in for a company's logo.
1065
1072
  - **`use_form`** — `useForm`: THE batch draft-form state hook — `values` = `initialValues` +
1066
1073
  an edits overlay (a revalidation refreshes untouched fields, no sync effect), `validate`
1067
1074
  (sync/async, gates submit, editing clears the field's error), `onSubmit(values, helpers)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "45.6.1",
3
+ "version": "45.7.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./vite": {
@@ -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 type BrandName = "facebook" | "zalo" | "linkedin" | "x" | "google-meet";
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 {
@@ -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
- {!!icon && <Icon name={icon} size={20} />}
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}