@colixsystems/widget-sdk 0.245.0 → 0.247.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.
Files changed (56) hide show
  1. package/README.md +60 -2
  2. package/dist/button-tokens.js +154 -0
  3. package/dist/button-view.js +251 -93
  4. package/dist/button.js +5 -3
  5. package/dist/button.native.js +6 -4
  6. package/dist/chart-logic.js +575 -0
  7. package/dist/chart-tokens.js +131 -0
  8. package/dist/chart-view.js +262 -0
  9. package/dist/chart.js +21 -0
  10. package/dist/chart.native.js +10 -0
  11. package/dist/contract.cjs +132 -35
  12. package/dist/contract.js +132 -35
  13. package/dist/detail-parts-view.js +31 -2
  14. package/dist/file-image-logic.js +162 -0
  15. package/dist/file-image-source.js +71 -0
  16. package/dist/file-image-view.js +119 -0
  17. package/dist/file-image.js +8 -0
  18. package/dist/file-image.native.js +8 -0
  19. package/dist/form-control-logic.js +23 -0
  20. package/dist/form-control-tokens.js +25 -10
  21. package/dist/grid-logic.js +222 -0
  22. package/dist/grid-view.js +109 -0
  23. package/dist/grid.js +7 -0
  24. package/dist/grid.native.js +7 -0
  25. package/dist/host.d.ts +6 -0
  26. package/dist/host.js +2 -0
  27. package/dist/index.d.ts +481 -20
  28. package/dist/index.js +14 -1
  29. package/dist/index.native.js +14 -1
  30. package/dist/menu-view.js +168 -0
  31. package/dist/overlay-tokens.js +50 -0
  32. package/dist/overlay-view.js +67 -24
  33. package/dist/overlay.native.js +3 -1
  34. package/dist/people-picker-logic.js +203 -0
  35. package/dist/people-picker-view.js +247 -0
  36. package/dist/people-picker.js +11 -0
  37. package/dist/people-picker.native.js +9 -0
  38. package/dist/popover-tokens.js +145 -0
  39. package/dist/popover-view.js +252 -0
  40. package/dist/popover.js +23 -0
  41. package/dist/popover.native.js +22 -0
  42. package/dist/radio-group-logic.js +61 -0
  43. package/dist/radio-group-tokens.js +62 -0
  44. package/dist/radio-group-view.js +262 -0
  45. package/dist/radio-group.js +12 -0
  46. package/dist/radio-group.native.js +7 -0
  47. package/dist/select-search-logic.js +150 -0
  48. package/dist/select-view.js +255 -43
  49. package/dist/select.js +6 -2
  50. package/dist/select.native.js +5 -3
  51. package/dist/tabs-tokens.js +59 -0
  52. package/dist/tabs-view.js +9 -43
  53. package/dist/tooltip-view.js +137 -0
  54. package/dist/web-modal.js +95 -23
  55. package/package.json +1 -1
  56. package/src/contract.js +132 -35
package/dist/index.d.ts CHANGED
@@ -2406,6 +2406,60 @@ export const WIDE_WIDTH_PX: number;
2406
2406
  */
2407
2407
  export function widthTier(width: number): "narrow" | "medium" | "wide";
2408
2408
 
2409
+ /** How a `<Grid>` splits its cells into lines. */
2410
+ export type GridBalance = "cards" | "tiles";
2411
+
2412
+ /** A theme spacing name, or a number of pixels. */
2413
+ export type GridGap = "xs" | "sm" | "md" | "lg" | "xl" | number;
2414
+
2415
+ /**
2416
+ * Lays its children out in balanced lines of cells, sized from the grid's own
2417
+ * measured width, on the web Player and the native export alike. It measures
2418
+ * an inner box with no padding, so a `style` padding is not counted twice.
2419
+ *
2420
+ * - `balance="cards"` (default): as many cells per line as fit at
2421
+ * `minCellWidth` (240 when unset); every line but the last is full, and the
2422
+ * last is padded with empty cells so its cards keep the width of the lines
2423
+ * above. 6 that fit 5 are 3 + 3.
2424
+ * - `balance="tiles"`: the remainder spreads over every line and each line
2425
+ * stretches, so 5 tiles at 4 per line are 3 + 2, never 4 + 1. The per-line
2426
+ * ceiling is `maxColumns`, or what fits at `minCellWidth`, or else 4 / 3 / 2
2427
+ * for the wide / medium / narrow width tier.
2428
+ * - `columns`: exactly that many cells per line, the last line padded.
2429
+ * - `cell={{ min, max }}`: definite-size cells (calendar days, seats,
2430
+ * swatches) as large as fit up to `max`, never below `min` (32 when unset),
2431
+ * in a block centred in the grid (a block whose `min` cells cannot fit
2432
+ * starts at the leading edge, so only its far end is cut off); with
2433
+ * `columns` a fixed count per line. `square` makes each cell as tall as it
2434
+ * is wide.
2435
+ *
2436
+ * Each child is one cell and keeps its own look; a child with `flex: 1` fills
2437
+ * its cell's height, so a line of cards ends level. Nothing is drawn until the
2438
+ * width is measured when the layout depends on it. In a row, give the grid
2439
+ * `style={{ flex: 1, minWidth: 0 }}` so it has a width to measure.
2440
+ *
2441
+ * @example
2442
+ * <Grid minCellWidth={240}>{items.map((item) => <Card key={item.id}>…</Card>)}</Grid>
2443
+ * <Grid columns={7} cell={{ min: 32, max: 64 }} square gap="xs">{days}</Grid>
2444
+ */
2445
+ export const Grid: (props: {
2446
+ children?: ReactNode;
2447
+ balance?: GridBalance;
2448
+ /** The narrowest a cell may be; the per-line count is derived from it. */
2449
+ minCellWidth?: number;
2450
+ maxColumns?: number;
2451
+ columns?: number;
2452
+ cell?: { min?: number; max: number };
2453
+ square?: boolean;
2454
+ /** Space between cells and lines; `"md"` when unset. */
2455
+ gap?: GridGap;
2456
+ /** The grid's outer box; padding here stays out of the measured width. */
2457
+ style?: any;
2458
+ testID?: string;
2459
+ /** Names the grid as one group to a screen reader. */
2460
+ accessibilityLabel?: string;
2461
+ }) => any;
2462
+
2409
2463
  /** Pass-through options for `useGeolocation().getCurrentPosition(...)`. */
2410
2464
  export interface GeolocationOptions {
2411
2465
  enableHighAccuracy?: boolean;
@@ -3140,6 +3194,8 @@ export interface AppUserGroupRow {
3140
3194
 
3141
3195
  export interface GroupsQuery {
3142
3196
  q?: string;
3197
+ /** Only these groups (1 to 100 ids): name the groups behind ids you hold. */
3198
+ ids?: string[];
3143
3199
  limit?: number;
3144
3200
  offset?: number;
3145
3201
  }
@@ -3870,6 +3926,11 @@ export const Avatar: (props: {
3870
3926
  seed?: string;
3871
3927
  /** An absolute image URL. */
3872
3928
  url?: string;
3929
+ /**
3930
+ * A filestore file id (a FILE column's value). Takes precedence over `url`;
3931
+ * the initials show while it loads and stay if it cannot load.
3932
+ */
3933
+ fileId?: string | null;
3873
3934
  /** A size token, or a number of px (clamped to 16-160). */
3874
3935
  size?: "sm" | "md" | "lg" | "xl" | number;
3875
3936
  shape?: "circle" | "rounded";
@@ -3878,6 +3939,36 @@ export const Avatar: (props: {
3878
3939
  testID?: string;
3879
3940
  }) => any;
3880
3941
 
3942
+ /**
3943
+ * A stored file's picture, or one at a URL: a themed `<Skeleton>` while it
3944
+ * loads and a fallback, never a broken image, when it cannot. A `fileId` is
3945
+ * resolved through `useFilestoreFile`, and an expired signed URL is fetched
3946
+ * again once before the fallback shows.
3947
+ *
3948
+ * @example
3949
+ * <FileImage fileId={record.Photo} variant="card" aspectRatio={4 / 3} />
3950
+ */
3951
+ export const FileImage: (props: {
3952
+ /** A filestore file id (a FILE column's value). Wins over `uri`. */
3953
+ fileId?: string | null;
3954
+ /** A URL you already have, e.g. a `useFilestoreFiles()` row's `urls.card`. */
3955
+ uri?: string | null;
3956
+ /** The size-ladder rung to load. Default "large". */
3957
+ variant?: "thumbnail" | "card" | "large" | "hero" | "original";
3958
+ /** Width / height. Unset, the box takes the picture's own shape once loaded. */
3959
+ aspectRatio?: number;
3960
+ /** Corner radius: px, a theme radius name, or "none". Default the theme's `md`. */
3961
+ radius?: number | "none" | "sm" | "md" | "lg" | "pill";
3962
+ resizeMode?: "cover" | "contain" | "stretch" | "center";
3963
+ /** What shows when the picture cannot load. Default "icon". */
3964
+ fallback?: "icon" | "blank";
3965
+ /** Unset, the picture is decorative and hidden from assistive technology. */
3966
+ accessibilityLabel?: string;
3967
+ style?: any;
3968
+ /** Also tags `<testID>-image`, `<testID>-loading` and `<testID>-fallback`. */
3969
+ testID?: string;
3970
+ }) => any;
3971
+
3881
3972
  /** Themed completion bar or ring for real progress toward a known goal. */
3882
3973
  export const Progress: (props: {
3883
3974
  /** Percent complete; clamped to 0-100. */
@@ -3979,38 +4070,61 @@ export const Card: (props: {
3979
4070
  export type CardScrim = "bottom" | "top" | "left" | "right" | "full" | "none";
3980
4071
  export type CardImagePosition = "center" | "top" | "bottom" | "left" | "right";
3981
4072
 
3982
- export type ButtonVariant = "primary" | "secondary" | "outlined" | "danger";
4073
+ export type ButtonVariant = "primary" | "secondary" | "outlined" | "danger" | "ghost" | "link";
4074
+ export type ButtonSize = "sm" | "md" | "lg";
3983
4075
  export const BUTTON_VARIANTS: readonly ButtonVariant[];
3984
4076
 
4077
+ /** Per-button surface fields; unset ones follow the theme. */
4078
+ export interface ButtonFields {
4079
+ background?: string;
4080
+ textColor?: string;
4081
+ borderColor?: string;
4082
+ borderWidth?: number;
4083
+ paddingX?: number;
4084
+ paddingY?: number;
4085
+ radius?: number;
4086
+ fontWeight?: string;
4087
+ fontSize?: number;
4088
+ shadow?: "none" | "sm" | "md" | "lg";
4089
+ gradient?: { from: string; to: string; angle?: number };
4090
+ }
4091
+
3985
4092
  /**
3986
4093
  * Themed button (Design > Components > Buttons, Danger buttons, Button outline). Omit
3987
- * `onPress` for a purely visual button inside something pressable.
4094
+ * `onPress` for a purely visual button inside something pressable. `ghost` and
4095
+ * `link` draw no fill or border and take only the look's shape and type.
3988
4096
  *
3989
4097
  * @example
3990
- * <Button title="Save" onPress={save} />
4098
+ * <Button title="Save" onPress={save} busy={saving} />
3991
4099
  * <Button title="Delete" variant="danger" icon="trash-2" onPress={remove} />
4100
+ * <Button title="Load more" variant="ghost" size="sm" onPress={next} />
3992
4101
  */
3993
- export const Button: (props: {
4102
+ /**
4103
+ * What a `<Menu>`, `<Popover>` or `<Tooltip>` puts on the button it wraps, so
4104
+ * a button can open one and expose whether it is open.
4105
+ */
4106
+ export interface ButtonTriggerProps {
4107
+ /** A `<Tooltip>` shows on a long press. */
4108
+ onLongPress?: () => void;
4109
+ "aria-haspopup"?: "menu" | "dialog" | boolean;
4110
+ "aria-expanded"?: boolean;
4111
+ "aria-describedby"?: string;
4112
+ }
4113
+
4114
+ export const Button: (props: ButtonTriggerProps & {
3994
4115
  title: ReactNode;
3995
4116
  variant?: ButtonVariant;
4117
+ /** sm (36 high) | md (44, default) | lg (52). */
4118
+ size?: ButtonSize;
3996
4119
  icon?: string;
3997
4120
  iconPosition?: "leading" | "trailing";
3998
4121
  onPress?: () => void;
3999
4122
  disabled?: boolean;
4000
- /** Per-button surface fields; unset ones follow the theme. */
4001
- fields?: {
4002
- background?: string;
4003
- textColor?: string;
4004
- borderColor?: string;
4005
- borderWidth?: number;
4006
- paddingX?: number;
4007
- paddingY?: number;
4008
- radius?: number;
4009
- fontWeight?: string;
4010
- fontSize?: number;
4011
- shadow?: "none" | "sm" | "md" | "lg";
4012
- gradient?: { from: string; to: string; angle?: number };
4013
- };
4123
+ /** A pending action: a spinner over the kept label; it cannot be pressed. */
4124
+ busy?: boolean;
4125
+ /** A toggle's on/off state; omit for a plain button. */
4126
+ selected?: boolean;
4127
+ fields?: ButtonFields;
4014
4128
  style?: any;
4015
4129
  labelStyle?: any;
4016
4130
  accessibilityLabel?: string;
@@ -4019,6 +4133,34 @@ export const Button: (props: {
4019
4133
  ref?: { current: any } | ((node: any) => void);
4020
4134
  }) => any;
4021
4135
 
4136
+ /**
4137
+ * A square themed button that shows only an icon, at least 36px on both hosts.
4138
+ * `accessibilityLabel` is its only name, so it is required.
4139
+ *
4140
+ * @example
4141
+ * <IconButton icon="pencil" accessibilityLabel="Edit order" onPress={edit} />
4142
+ * <IconButton icon="bold" accessibilityLabel="Bold" selected={bold} onPress={toggleBold} />
4143
+ */
4144
+ export const IconButton: (props: ButtonTriggerProps & {
4145
+ /** A lucide icon name, drawn with `<Icon>`. */
4146
+ icon: string;
4147
+ accessibilityLabel: string;
4148
+ /** ghost (default) | primary | secondary | outlined | danger. */
4149
+ variant?: Exclude<ButtonVariant, "link">;
4150
+ /** sm (36) | md (44, default) | lg (52). */
4151
+ size?: ButtonSize;
4152
+ /** The glyph's colour, over the variant's. */
4153
+ color?: string;
4154
+ onPress?: () => void;
4155
+ disabled?: boolean;
4156
+ busy?: boolean;
4157
+ selected?: boolean;
4158
+ fields?: ButtonFields;
4159
+ style?: any;
4160
+ testID?: string;
4161
+ ref?: { current: any } | ((node: any) => void);
4162
+ }) => any;
4163
+
4022
4164
  /**
4023
4165
  * What every form control shares: the label drawn with it (and read by a
4024
4166
  * screen reader), a hint below it, and an error that replaces the hint and
@@ -4101,12 +4243,53 @@ export interface SelectOption<V extends string | number = string | number> {
4101
4243
  label: string;
4102
4244
  disabled?: boolean;
4103
4245
  testID?: string;
4246
+ /** Drawn before the label in the sheet: an `<Avatar>`, an icon. */
4247
+ leading?: ReactNode;
4248
+ }
4249
+
4250
+ /**
4251
+ * The searchable mode of `<Select>`: a `<SearchField>` heads the sheet. Turn
4252
+ * it on with `searchable` (the options are filtered where they are held),
4253
+ * `onSearch` (you fetch per query) or async `options` (Select fetches).
4254
+ */
4255
+ export interface SelectSearchProps<V extends string | number = string | number> {
4256
+ /** Filter the given `options` by label as the person types. */
4257
+ searchable?: boolean;
4258
+ /**
4259
+ * Called with the trimmed query when the sheet opens ("") and once typing
4260
+ * pauses; answer by passing new `options` (and `loading` meanwhile).
4261
+ */
4262
+ onSearch?: (query: string) => void;
4263
+ /** The pause, in ms, before a typed query is sent. Default 250. */
4264
+ searchDebounceMs?: number;
4265
+ /** The options for the last query are on their way. */
4266
+ loading?: boolean;
4267
+ /** The last query failed; a string replaces `loadErrorText`. */
4268
+ optionsError?: boolean | string;
4269
+ /**
4270
+ * The options behind picked values the current `options` may not hold (a
4271
+ * saved choice outside the first page), so the trigger names them.
4272
+ */
4273
+ selectedOptions?: Array<SelectOption<V>>;
4274
+ /** Default "Search". */
4275
+ searchPlaceholder?: string;
4276
+ /** Shown when a query finds nothing. Default "No results". */
4277
+ noResultsText?: string;
4278
+ /** Default "Loading…". */
4279
+ loadingText?: string;
4280
+ /** Default "Could not load the options". */
4281
+ loadErrorText?: string;
4104
4282
  }
4105
4283
 
4106
4284
  /** What every `<Select>` takes, in either mode. */
4107
4285
  export type SelectBaseProps<V extends string | number = string | number> = FormControlProps &
4108
- FormFieldOverrides & {
4109
- options: Array<SelectOption<V>>;
4286
+ FormFieldOverrides &
4287
+ SelectSearchProps<V> & {
4288
+ /**
4289
+ * The options, or a function Select calls with each query (searchable
4290
+ * mode) that resolves to them; only its latest answer is shown.
4291
+ */
4292
+ options: Array<SelectOption<V>> | ((query: string) => Promise<Array<SelectOption<V>>>);
4110
4293
  /** Shown while nothing is chosen. Default "Select". */
4111
4294
  placeholder?: string;
4112
4295
  /** The sheet's heading. Defaults to a text `label`. */
@@ -4134,6 +4317,11 @@ export type SelectBaseProps<V extends string | number = string | number> = FormC
4134
4317
  * trigger shows the picked labels as small chips while they fit on its line,
4135
4318
  * else "N selected".
4136
4319
  *
4320
+ * Searchable (`searchable`, `onSearch` or async `options`), the sheet leads
4321
+ * with a `<SearchField>`, says when it is loading and when nothing matches,
4322
+ * and keeps the label of every option it has shown; its rows take the theme's
4323
+ * Lists row height and padding when they are set.
4324
+ *
4137
4325
  * @example
4138
4326
  * <Select label="Status" value={status} onValueChange={setStatus}
4139
4327
  * options={[{ value: "open", label: "Open" }, { value: "done", label: "Done" }]} />
@@ -4160,6 +4348,62 @@ export const Select: <V extends string | number = string | number>(
4160
4348
  }),
4161
4349
  ) => any;
4162
4350
 
4351
+ /** Where `<PeoplePicker>` reads its options. */
4352
+ export type PeoplePickerSource = "users" | "groups" | "records";
4353
+
4354
+ /**
4355
+ * A searchable `<Select>` of the workspace's users (`useDirectory`, scope
4356
+ * `directory.read:users`), its groups (`useGroups`, `groups.read:*`) or one
4357
+ * table's records (`useDatastoreQuery`, the table's `datastore.read` scope).
4358
+ * Nothing loads until the sheet opens; each pause in typing searches on the
4359
+ * server. A saved value the page does not hold is looked up once by id, so the
4360
+ * trigger shows its name, `loadingText` meanwhile, or `unknownText` when it is
4361
+ * gone, never the id. Every other `<Select>` prop works.
4362
+ *
4363
+ * @example
4364
+ * <PeoplePicker label="Assignee" value={assignee} onValueChange={setAssignee} />
4365
+ * <PeoplePicker source="groups" multiple label="Teams" values={teams} onValuesChange={setTeams} />
4366
+ * <PeoplePicker source="records" table={tableId} label="Customer" value={customerId}
4367
+ * onValueChange={setCustomerId} />
4368
+ */
4369
+ export const PeoplePicker: (
4370
+ props: Omit<SelectBaseProps<string>, "options" | "onSearch" | "selectedOptions" | "searchable"> & {
4371
+ /** Default "users". */
4372
+ source?: PeoplePickerSource;
4373
+ /** `source="records"`: the table id. */
4374
+ table?: string;
4375
+ /** `source="records"`: the column that names a record. Default the identification column. */
4376
+ labelColumn?: string;
4377
+ /** `source="records"`: a FILE column drawn as each row's avatar. */
4378
+ avatarColumn?: string;
4379
+ /** `source="users"`: only members of this group (a `useGroups()` row id). */
4380
+ groupId?: string;
4381
+ /** `source="records"`: narrows the rows, as `useDatastoreQuery`'s `filter`. */
4382
+ filter?: Query["filter"];
4383
+ /** Ids never offered (already members, the signed-in user). */
4384
+ excludeIds?: string[];
4385
+ /** Rows per search. Default 50, at most 200. */
4386
+ pageSize?: number;
4387
+ /** Draw an avatar before each user or group. Default true. */
4388
+ showAvatars?: boolean;
4389
+ /** A saved value whose row is gone, or a row with no name. Default "Unknown". */
4390
+ unknownText?: string;
4391
+ } & (
4392
+ | {
4393
+ multiple?: false;
4394
+ value?: string | null;
4395
+ onValueChange?: (value: string, option: SelectOption<string>) => void;
4396
+ }
4397
+ | {
4398
+ multiple: true;
4399
+ values?: string[] | null;
4400
+ onValuesChange?: (values: string[], options: Array<SelectOption<string>>) => void;
4401
+ selectedCountText?: (count: number) => string;
4402
+ doneText?: string;
4403
+ }
4404
+ ),
4405
+ ) => any;
4406
+
4163
4407
  /**
4164
4408
  * Space-key activation for a control whose role is not a button: on the web a
4165
4409
  * `Pressable` answers Space only for a button. Native ignores `onKeyDown`.
@@ -4204,6 +4448,48 @@ export const SearchField: (props: FormControlProps & {
4204
4448
  [prop: string]: any;
4205
4449
  }) => any;
4206
4450
 
4451
+ export type RadioGroupVariant = "list" | "card" | "segmented";
4452
+
4453
+ export interface RadioOption<V extends string | number = string | number> {
4454
+ value: V;
4455
+ label: ReactNode;
4456
+ /** A second line under the label; not drawn by the segmented variant. */
4457
+ description?: ReactNode;
4458
+ /** A lucide icon name drawn before the label. */
4459
+ icon?: string;
4460
+ disabled?: boolean;
4461
+ accessibilityLabel?: string;
4462
+ testID?: string;
4463
+ }
4464
+
4465
+ /**
4466
+ * Themed pick-ONE of a short list that stays on screen. `list` (default) is
4467
+ * the app's input panel with one row per option, `card` one bordered card per
4468
+ * option (with its `description`), both from Design > Components > Inputs;
4469
+ * `segmented` is the segmented tab strip from Design > Components > Tabs,
4470
+ * its options sharing the row evenly. Radio role and checked state on both
4471
+ * hosts; on the web the group is one tab stop, the arrow keys move the
4472
+ * choice and Space picks the focused option. Other props land on the group.
4473
+ *
4474
+ * @example
4475
+ * <RadioGroup label="Delivery" value={speed} onValueChange={setSpeed}
4476
+ * options={[{ value: "standard", label: "Standard" }, { value: "express", label: "Express" }]} />
4477
+ */
4478
+ export const RadioGroup: <V extends string | number = string | number>(
4479
+ props: FormControlProps & {
4480
+ options: Array<RadioOption<V>>;
4481
+ value?: V | null;
4482
+ /** Called when a different option is picked. */
4483
+ onValueChange?: (value: V, option: RadioOption<V>) => void;
4484
+ variant?: RadioGroupVariant;
4485
+ /** The group. */
4486
+ style?: any;
4487
+ /** The box around a group that shows a label, helper or error. */
4488
+ containerStyle?: any;
4489
+ [prop: string]: any;
4490
+ },
4491
+ ) => any;
4492
+
4207
4493
  export type TabStyle = "segmented" | "pill" | "underline";
4208
4494
 
4209
4495
  /**
@@ -4334,6 +4620,80 @@ export const ChipGroup: (props: {
4334
4620
  testID?: string;
4335
4621
  }) => any;
4336
4622
 
4623
+ export type ChartType = "bar" | "hbar" | "line" | "area" | "donut" | "sparkline";
4624
+ export type ChartValueFormat = "number" | "compact" | "percent" | ((value: number) => string);
4625
+
4626
+ /** One series of a multi-series `<Chart>`: the key each `data` item carries its value under. */
4627
+ export interface ChartSeries {
4628
+ key: string;
4629
+ label: string;
4630
+ /** A colour of the series' own; leave unset so the theme's chart look applies. */
4631
+ color?: string;
4632
+ }
4633
+
4634
+ /**
4635
+ * Themed chart drawn with react-native-svg. Its series colours, axis and label
4636
+ * colour, gridline colour and bar corners come from the theme's `chart` scope
4637
+ * (falling back to the theme's colours), so every chart in the app matches. It
4638
+ * measures its own width, shows a `<Skeleton>` while `loading` and an
4639
+ * `<EmptyState>` when there is nothing to plot, draws a legend for a donut and
4640
+ * for several series, and gives a screen reader a summary and the values.
4641
+ *
4642
+ * @example
4643
+ * const { groups, loading } = useDatastoreAggregate(tableId, { groupBy: "city", sumColumn: "visits" });
4644
+ * <Chart type="bar" loading={loading}
4645
+ * data={groups.map((g) => ({ label: String(g.group), value: Number(g.sum) || 0 }))} />
4646
+ */
4647
+ export const Chart: (props: {
4648
+ /** Default "bar". "hbar" is horizontal bars; "sparkline" a small line with no axes. */
4649
+ type?: ChartType;
4650
+ /** `[{ label, value }]`, or with `series` one number per series key. */
4651
+ data: Array<{ label: string | number | { label: string }; [key: string]: unknown }>;
4652
+ /** Several series sharing `data`. A donut and a sparkline draw the first. */
4653
+ series?: ChartSeries[];
4654
+ /**
4655
+ * How values read on the axis, the legend and to a screen reader, grouped
4656
+ * for the app's language. Default "number". "percent" appends "%" to values
4657
+ * already in percent (42 reads "42%"); it does not multiply by 100.
4658
+ */
4659
+ valueFormat?: ChartValueFormat;
4660
+ /** The plot's height in px. Default 220, or 48 for a sparkline. */
4661
+ height?: number;
4662
+ loading?: boolean;
4663
+ /** Defaults to the shared "No results" text. */
4664
+ emptyTitle?: string;
4665
+ emptyMessage?: string;
4666
+ /** Replaces the spoken summary sentence; the values are still read after it. */
4667
+ accessibilityLabel?: string;
4668
+ /** The tail of a spoken value list cut short; default `(count) => \`and ${count} more\``. */
4669
+ moreText?: (count: number) => string;
4670
+ /** For hosts rendering outside a widget context; widgets omit it. */
4671
+ theme?: any;
4672
+ style?: any;
4673
+ testID?: string;
4674
+ }) => any;
4675
+
4676
+ /**
4677
+ * The colours `<Chart>` paints with, for a chart a widget draws itself (a
4678
+ * scatter plot, a timeline) so it matches Design > Components > Charts.
4679
+ *
4680
+ * @example
4681
+ * const colors = chartColors(useTheme());
4682
+ * <Circle fill={colors.series(0)} fillOpacity={colors.seriesOpacity(0)} />
4683
+ */
4684
+ export function chartColors(theme: any): {
4685
+ /** The six series colours, in order. */
4686
+ palette: string[];
4687
+ /** Axis line and label colour. */
4688
+ axis: string;
4689
+ /** Gridline colour. */
4690
+ grid: string;
4691
+ /** Series `index`'s colour; a seventh and later one is lighter. */
4692
+ series(index: number): string;
4693
+ /** Series `index`'s opacity: 1, below 1 for a repeat of a colour that cannot be lightened. */
4694
+ seriesOpacity(index: number): number;
4695
+ };
4696
+
4337
4697
  /**
4338
4698
  * A file as `<FilePicker>` and `<FileUpload>` hand it over: the browser's
4339
4699
  * `File` on the web, expo-file-system's `File` on the Expo export. Pass it to
@@ -4429,6 +4789,107 @@ export const FileUpload: (props: FormControlProps & {
4429
4789
  containerStyle?: any;
4430
4790
  }) => any;
4431
4791
 
4792
+ export type PopoverPlacement = "bottom" | "top";
4793
+
4794
+ /** One `<Menu>` item. */
4795
+ export interface MenuItem {
4796
+ label: string;
4797
+ /** Runs after the menu closes. */
4798
+ onPress?: () => void;
4799
+ /** A Lucide icon name, or an element drawn before the label. */
4800
+ icon?: string | ReactNode;
4801
+ /** Drawn in the theme's danger colour. */
4802
+ danger?: boolean;
4803
+ /** Dimmed, skipped by the arrow keys, and cannot be picked. */
4804
+ disabled?: boolean;
4805
+ accessibilityLabel?: string;
4806
+ testID?: string;
4807
+ key?: string;
4808
+ }
4809
+
4810
+ /**
4811
+ * A list of actions opened from a trigger: a row's "…" button, a toolbar's
4812
+ * overflow. Beside the trigger on a wide screen, a bottom sheet on a phone;
4813
+ * drawn over the screen, so no card or list clips it. The backdrop, Escape
4814
+ * (web) and the Android back button close it, and focus returns to the
4815
+ * trigger. On the web it is a `menu` of `menuitem`s: the arrow keys, Home and
4816
+ * End move between enabled items and Space or Enter picks one. A picked
4817
+ * item's `onPress` runs once the menu has left the screen, so it can open a
4818
+ * `<ConfirmDialog>`. Follows the theme's `popover` scope (Design > Components
4819
+ * > Dropdown menus).
4820
+ *
4821
+ * @example
4822
+ * <Menu
4823
+ * trigger={<IconButton icon="more-horizontal" accessibilityLabel="More actions" />}
4824
+ * accessibilityLabel="Order actions"
4825
+ * items={[
4826
+ * { label: "Edit", icon: "pencil", onPress: edit },
4827
+ * canDelete && { label: "Delete", icon: "trash-2", danger: true, onPress: askDelete },
4828
+ * ]}
4829
+ * />
4830
+ */
4831
+ export const Menu: (props: {
4832
+ /** ONE pressable element; the menu adds the press that opens it. */
4833
+ trigger: ReactNode;
4834
+ /** Falsy entries are skipped, so `cond && { … }` works. */
4835
+ items: Array<MenuItem | null | false | undefined>;
4836
+ accessibilityLabel?: string;
4837
+ /** Default "bottom"; it flips at the screen edge. */
4838
+ placement?: PopoverPlacement;
4839
+ /** The sheet backdrop's name. Default "Close". */
4840
+ closeAccessibilityLabel?: string;
4841
+ /** On the panel; the item list takes `-items`. */
4842
+ testID?: string;
4843
+ }) => any;
4844
+
4845
+ /**
4846
+ * Free content beside a trigger: quick filters, a swatch picker, a short
4847
+ * detail card. Placed like `<Menu>`, closed the same ways. Components inside
4848
+ * read a theme inked for the panel. Follows the theme's `popover` scope.
4849
+ *
4850
+ * @example
4851
+ * <Popover trigger={<Button variant="secondary" title="Filters" />} accessibilityLabel="Filters">
4852
+ * {({ close }) => <Button title="Apply" onPress={() => { apply(); close(); }} />}
4853
+ * </Popover>
4854
+ */
4855
+ export const Popover: (props: {
4856
+ /** ONE pressable element; the popover adds the press that opens it. */
4857
+ trigger: ReactNode;
4858
+ children?: ReactNode | ((api: { close: () => void }) => ReactNode);
4859
+ /** Pass with `onVisibleChange` to open and close it from your own code. */
4860
+ visible?: boolean;
4861
+ onVisibleChange?: (visible: boolean) => void;
4862
+ /** Default "bottom"; it flips at the screen edge. */
4863
+ placement?: PopoverPlacement;
4864
+ accessibilityLabel?: string;
4865
+ /** The sheet backdrop's name. Default "Close". */
4866
+ closeAccessibilityLabel?: string;
4867
+ /** On the panel. */
4868
+ testID?: string;
4869
+ }) => any;
4870
+
4871
+ /**
4872
+ * A short label for the one pressable element it wraps, usually an
4873
+ * icon-only button that keeps its own `accessibilityLabel`. On the web it
4874
+ * shows on hover and keyboard focus; a touch screen has no hover, so there a
4875
+ * long press shows it (a bottom sheet on a phone). A child with its own
4876
+ * `onLongPress` keeps it and gets no long-press tooltip. The tooltip wraps
4877
+ * the child in a `View`. Follows the theme's `popover` scope.
4878
+ *
4879
+ * @example
4880
+ * <Tooltip label="Delete">
4881
+ * <IconButton icon="trash-2" accessibilityLabel="Delete" onPress={remove} />
4882
+ * </Tooltip>
4883
+ */
4884
+ export const Tooltip: (props: {
4885
+ label: string;
4886
+ /** Default "top"; it flips at the screen edge. */
4887
+ placement?: PopoverPlacement;
4888
+ /** On the label. */
4889
+ testID?: string;
4890
+ children: ReactNode;
4891
+ }) => any;
4892
+
4432
4893
  /**
4433
4894
  * Themed collapsible sections. The surface comes from the theme's `card`
4434
4895
  * scope and the headers and dividers from its `list` scope, so every
package/dist/index.js CHANGED
@@ -112,6 +112,8 @@ export {
112
112
  widthTier,
113
113
  WIDE_WIDTH_PX,
114
114
  } from "./container-width.js";
115
+ // Balanced lines of cells sized from the grid's own measured width.
116
+ export { Grid } from "./grid.js";
115
117
  // REQ-WSDK-PLATFORM §6 — Tier A hooks. Each ships in a per-platform file
116
118
  // (./clipboard.js / .native.js, ./toast.js / .native.js); index.js picks
117
119
  // the web variant and index.native.js picks the native variant.
@@ -241,6 +243,8 @@ export { List, ListRow, ListHeader, EmptyState } from "./list.js";
241
243
  export { Badge, Avatar, Progress, Stat, Divider } from "./detail-parts.js";
242
244
  export { initialsFor } from "./initials.js";
243
245
  export { avatarColorFor } from "./avatar-color.js";
246
+ // A stored file's picture: a themed placeholder, then a fallback on failure.
247
+ export { FileImage } from "./file-image.js";
244
248
  // The one card surface: the theme's `card` scope, a gradient fill, margin.
245
249
  export { Card } from "./card.js";
246
250
  export {
@@ -258,7 +262,7 @@ export {
258
262
  cardGradientProps,
259
263
  } from "./card-style.js";
260
264
  // The themed button; follows the theme's button, destructive and ring scopes.
261
- export { Button, BUTTON_VARIANTS } from "./button.js";
265
+ export { Button, IconButton, BUTTON_VARIANTS } from "./button.js";
262
266
  export {
263
267
  BUTTON_PADDING_MAX,
264
268
  BUTTON_BORDER_WIDTH_MAX,
@@ -278,6 +282,10 @@ export { Checkbox } from "./checkbox.js";
278
282
  export { Switch } from "./switch.js";
279
283
  export { Select } from "./select.js";
280
284
  export { SearchField } from "./search-field.js";
285
+ // A searchable `<Select>` of users, groups or one table's records.
286
+ export { PeoplePicker } from "./people-picker.js";
287
+ // Pick one of a short visible list; `muted` scope, or `tabs` when segmented.
288
+ export { RadioGroup } from "./radio-group.js";
281
289
  // Space for a control whose role is not a button; a no-op on native.
282
290
  export { spaceActivates } from "./form-control-logic.js";
283
291
  // A themed tab strip; follows the theme's `tabs` scope.
@@ -292,8 +300,13 @@ export { Modal } from "./modal.js";
292
300
  export { ConfirmDialog } from "./confirm-dialog.js";
293
301
  // Themed filter chips, tags and pills; follow the theme's `chip` scope.
294
302
  export { Chip, ChipGroup } from "./chip.js";
303
+ // Themed bar, line, area and donut charts; follow the theme's `chart` scope.
304
+ export { Chart } from "./chart.js";
305
+ export { chartColors } from "./chart-tokens.js";
295
306
  // A themed file chooser on `<FilePicker>`; follows the theme's `muted` scope.
296
307
  export { FileUpload } from "./file-upload.js";
308
+ // Anchored actions, content and labels; follow the theme's `popover` scope.
309
+ export { Menu, Popover, Tooltip } from "./popover.js";
297
310
  // The semantic text tiers, shared with the built-in widgets.
298
311
  export {
299
312
  textRoleStyle,