@antscorp/ui 0.1.2 → 0.1.4

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 (99) hide show
  1. package/README.md +26 -2
  2. package/dist/atoms/AddButton.d.ts +3 -6
  3. package/dist/atoms/Chip.d.ts +2 -5
  4. package/dist/atoms/Chip.js +1 -1
  5. package/dist/atoms/InfoTip.js +2 -0
  6. package/dist/atoms/LabeledDivider.d.ts +1 -1
  7. package/dist/atoms/PanelSearchInput.d.ts +1 -1
  8. package/dist/atoms/RemoveButton.d.ts +2 -5
  9. package/dist/atoms/TextAction.d.ts +5 -6
  10. package/dist/atoms/TruncatedOption.d.ts +1 -1
  11. package/dist/cli.js +248 -0
  12. package/dist/doc/schema.d.ts +34 -0
  13. package/dist/doc/schema.js +33 -0
  14. package/dist/docs.json +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +53 -51
  17. package/dist/lib/iso.d.ts +2 -2
  18. package/dist/lib/iso.js +16 -8
  19. package/dist/lib/tokenEditorExtensions.js +1 -1
  20. package/dist/lib/useLoadingShown.d.ts +1 -1
  21. package/dist/molecules/AccentCard.d.ts +2 -3
  22. package/dist/molecules/AccentCard.js +2 -2
  23. package/dist/molecules/CheckRow.d.ts +3 -8
  24. package/dist/molecules/ChoiceTiles.d.ts +1 -1
  25. package/dist/molecules/ChoiceTiles.js +1 -1
  26. package/dist/molecules/CommitTextField.d.ts +1 -19
  27. package/dist/molecules/CommitTextField.js +21 -18
  28. package/dist/molecules/ConfirmDialog.d.ts +2 -7
  29. package/dist/molecules/DateTimeInput.d.ts +6 -3
  30. package/dist/molecules/DateTimeInput.js +82 -73
  31. package/dist/molecules/FieldGroup.d.ts +1 -1
  32. package/dist/molecules/FieldRow.d.ts +1 -1
  33. package/dist/molecules/MetaTable.d.ts +1 -1
  34. package/dist/molecules/PhoneFrame.d.ts +1 -1
  35. package/dist/molecules/RemovableRow.d.ts +1 -1
  36. package/dist/molecules/RemovableRow.js +32 -32
  37. package/dist/molecules/RevealRadioGroup.d.ts +2 -9
  38. package/dist/molecules/RevealSwitch.d.ts +2 -7
  39. package/dist/molecules/Select.d.ts +8 -24
  40. package/dist/molecules/Select.js +50 -44
  41. package/dist/molecules/SideLabelRow.d.ts +1 -1
  42. package/dist/molecules/SplitButton.d.ts +1 -1
  43. package/dist/molecules/StatusButton.d.ts +2 -7
  44. package/dist/molecules/TagSelect.d.ts +2 -2
  45. package/dist/molecules/TransferLayout.d.ts +1 -1
  46. package/dist/molecules/TreeOptions.d.ts +4 -3
  47. package/dist/molecules/TreeOptions.js +67 -44
  48. package/dist/organisms/HourOfDayRow.js +2 -2
  49. package/dist/organisms/HourOfWeekGrid.js +2 -2
  50. package/dist/organisms/RuleGroups.d.ts +3 -3
  51. package/dist/organisms/SearchableGroupedList.d.ts +1 -1
  52. package/dist/organisms/TransferPicker.d.ts +1 -1
  53. package/dist/organisms/TreeSearchPanel.js +50 -47
  54. package/dist/organisms/TreeSelect.d.ts +3 -3
  55. package/dist/ui.css +1 -1
  56. package/package.json +36 -19
  57. package/src/atoms/AddButton.tsx +3 -7
  58. package/src/atoms/Chip.tsx +7 -9
  59. package/src/atoms/InfoTip.tsx +2 -2
  60. package/src/atoms/LabeledDivider.tsx +1 -1
  61. package/src/atoms/PanelSearchInput.tsx +1 -1
  62. package/src/atoms/RemoveButton.tsx +2 -6
  63. package/src/atoms/TextAction.tsx +6 -8
  64. package/src/atoms/TruncatedOption.tsx +1 -1
  65. package/src/doc/schema.ts +67 -0
  66. package/src/index.ts +2 -0
  67. package/src/lib/iso.ts +15 -3
  68. package/src/lib/tokenEditorExtensions.ts +1 -1
  69. package/src/lib/useLoadMore.ts +1 -2
  70. package/src/lib/useLoadingShown.ts +1 -1
  71. package/src/molecules/AccentCard.tsx +11 -10
  72. package/src/molecules/CheckRow.tsx +3 -9
  73. package/src/molecules/ChoiceTiles.tsx +6 -4
  74. package/src/molecules/CommitTextField.tsx +15 -20
  75. package/src/molecules/ConfirmDialog.tsx +2 -8
  76. package/src/molecules/DateTimeInput.tsx +86 -68
  77. package/src/molecules/DurationInput.tsx +2 -2
  78. package/src/molecules/FieldGroup.tsx +1 -1
  79. package/src/molecules/FieldRow.tsx +1 -1
  80. package/src/molecules/FrequencyLimitRow.tsx +1 -1
  81. package/src/molecules/MetaTable.tsx +1 -1
  82. package/src/molecules/PhoneFrame.tsx +2 -2
  83. package/src/molecules/RemovableRow.tsx +3 -3
  84. package/src/molecules/RevealRadioGroup.tsx +5 -10
  85. package/src/molecules/RevealSwitch.tsx +2 -8
  86. package/src/molecules/Select.tsx +23 -23
  87. package/src/molecules/SideLabelRow.tsx +2 -2
  88. package/src/molecules/SplitButton.tsx +1 -1
  89. package/src/molecules/StatusButton.tsx +2 -8
  90. package/src/molecules/TagSelect.tsx +5 -5
  91. package/src/molecules/TransferLayout.tsx +2 -3
  92. package/src/molecules/TreeOptions.tsx +89 -65
  93. package/src/organisms/HourOfDayRow.tsx +5 -3
  94. package/src/organisms/HourOfWeekGrid.tsx +5 -3
  95. package/src/organisms/RuleGroups.tsx +3 -3
  96. package/src/organisms/SearchableGroupedList.tsx +2 -2
  97. package/src/organisms/TransferPicker.tsx +3 -3
  98. package/src/organisms/TreeSearchPanel.tsx +2 -2
  99. package/src/organisms/TreeSelect.tsx +4 -4
package/package.json CHANGED
@@ -1,18 +1,24 @@
1
1
  {
2
2
  "name": "@antscorp/ui",
3
- "version": "0.1.2",
4
- "description": "Generic UI kit: atoms, molecules and organisms over Astryx, with no MarketingHub words.",
3
+ "version": "0.1.4",
4
+ "description": "Generic UI kit: atoms, molecules and organisms over Astryx.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://bitbucket.org/git-vn/at.antsomi-ui.git",
9
9
  "directory": "packages/ui"
10
10
  },
11
+ "bin": {
12
+ "antscorp-ui": "./dist/cli.js"
13
+ },
11
14
  "files": [
12
15
  "dist",
13
16
  "src",
17
+ "!src/cli/**",
14
18
  "!src/**/*.test.*",
15
19
  "!src/**/*.stories.*",
20
+ "!src/**/*.doc.ts",
21
+ "!dist/**/*.doc.*",
16
22
  "!src/**/__snapshots__/**",
17
23
  "!src/testing.tsx",
18
24
  "!src/story.tsx"
@@ -37,6 +43,11 @@
37
43
  "types": "./dist/astryx/*.d.ts",
38
44
  "default": "./dist/astryx/*.js"
39
45
  },
46
+ "./doc": {
47
+ "antscorp-source": "./src/doc/schema.ts",
48
+ "types": "./dist/doc/schema.d.ts",
49
+ "default": "./dist/doc/schema.js"
50
+ },
40
51
  "./stylex": {
41
52
  "antscorp-source": "./src/stylex.ts",
42
53
  "types": "./dist/stylex.d.ts",
@@ -88,38 +99,44 @@
88
99
  "scripts": {
89
100
  "astryx:reexports": "bun scripts/astryx-reexports.ts",
90
101
  "test": "bun scripts/astryx-reexports.ts --check && vitest run && AUI_REACT_COMPILER=off vitest run --coverage",
102
+ "test:perf": "PERF=1 vitest run",
91
103
  "typecheck": "tsc -p .",
92
- "build": "vite build --config vite.build.ts && tsc -p tsconfig.build.json && bun scripts/build-declarations.ts"
104
+ "build": "vite build --config vite.build.ts && tsc -p tsconfig.build.json && bun scripts/build-declarations.ts && node ../../tools/component-catalog/src/main.ts . && bun build src/cli/main.ts --target=node --outfile=dist/cli.js"
93
105
  },
94
106
  "dependencies": {
95
- "@antscorp/astryx-theme-antsomi": "0.1.2",
96
- "@antscorp/immutable": "0.1.0",
107
+ "@antscorp/astryx-theme-antsomi": "0.1.4",
108
+ "@antscorp/immutable": "0.1.1",
97
109
  "@astryxdesign/core": "0.7.4",
98
110
  "@codemirror/state": "6.7.6",
99
- "@codemirror/view": "6.43.14",
111
+ "@codemirror/view": "6.43.15",
100
112
  "@stylexjs/stylex": "0.19.1",
101
- "spin-delay": "2.0.1"
113
+ "spin-delay": "2.0.1",
114
+ "zod": "4.6.7"
102
115
  },
103
116
  "devDependencies": {
104
- "@antscorp/vite-stylex": "0.1.2",
105
- "@storybook/react-vite": "10.6.1",
117
+ "@antscorp/component-catalog": "0.1.0",
118
+ "@antscorp/vite-stylex": "0.1.4",
119
+ "@storybook/react-vite": "10.6.5",
106
120
  "@testing-library/dom": "10.4.2",
107
121
  "@testing-library/react": "16.3.3",
108
- "@testing-library/user-event": "14.6.7",
122
+ "@testing-library/user-event": "14.6.8",
109
123
  "@types/bun": "1.4.4",
110
- "@types/node": "24.19.1",
124
+ "@types/node": "24.19.2",
111
125
  "@types/react": "19.3.0",
112
126
  "@types/react-dom": "19.3.0",
113
- "@vitest/coverage-v8": "5.0.3",
127
+ "@vitest/coverage-v8": "5.0.5",
114
128
  "happy-dom": "20.14.5",
115
- "react": "19.3.0",
116
- "react-dom": "19.3.0",
117
- "storybook": "10.6.1",
118
- "vite": "8.3.3",
119
- "vitest": "5.0.3"
129
+ "react": "19.3.1",
130
+ "react-dom": "19.3.1",
131
+ "storybook": "10.6.5",
132
+ "vite": "8.3.5",
133
+ "vitest": "5.0.5"
120
134
  },
121
135
  "peerDependencies": {
122
- "react": ">=19",
123
- "react-dom": ">=19"
136
+ "react": ">=19.2",
137
+ "react-dom": ">=19.2"
138
+ },
139
+ "engines": {
140
+ "node": ">=24"
124
141
  }
125
142
  }
@@ -1,4 +1,4 @@
1
- import { Button } from "@astryxdesign/core/Button";
1
+ import { Button, type ButtonProps } from "@astryxdesign/core/Button";
2
2
  import { radiusVars } from "@astryxdesign/core/theme/tokens.stylex";
3
3
  import * as stylex from "@stylexjs/stylex";
4
4
  import type { ReactNode } from "react";
@@ -6,19 +6,15 @@ import type { ReactNode } from "react";
6
6
  import { AddGlyph } from "./AddGlyph";
7
7
 
8
8
  /** What `AddButton` takes. */
9
- export type AddButtonProps = {
10
- /** What it adds, such as "Add row"; the host translates it. */
11
- readonly label: string;
9
+ export type AddButtonProps = Pick<ButtonProps, "label" | "isDisabled"> & {
12
10
  /** `ghost` by default; `secondary` for an add that needs more weight. */
13
11
  readonly variant?: "ghost" | "secondary" | undefined;
14
12
  /** The button's height, `md` by default; its plus follows it. */
15
13
  readonly size?: "sm" | "md" | undefined;
16
14
  /** `default` keeps the button's own corners; `pill` rounds them fully; the theme colours its outline. */
17
15
  readonly shape?: "default" | "pill" | undefined;
18
- /** `sentence` (default) shows the label as given; `upper` in capitals, as fe-app's "+ AND". */
16
+ /** `sentence` (default) shows the label as given; `upper` in capitals, such as "+ AND". */
19
17
  readonly case?: "sentence" | "upper" | undefined;
20
- /** Dimmed and not pressable, e.g. when a limit is reached. */
21
- readonly isDisabled?: boolean | undefined;
22
18
  /** Read-only: an add is an editing affordance, so nothing is drawn. */
23
19
  readonly isReadOnly?: boolean | undefined;
24
20
  /** Called when the button is pressed. */
@@ -1,3 +1,4 @@
1
+ import type { AccentRole } from "@antscorp/astryx-theme-antsomi/palette";
1
2
  import { HStack } from "@astryxdesign/core/HStack";
2
3
  import { Icon } from "@astryxdesign/core/Icon";
3
4
  import { IconButton } from "@astryxdesign/core/IconButton";
@@ -9,7 +10,7 @@ import {
9
10
  spacingVars,
10
11
  typeScaleVars,
11
12
  } from "@astryxdesign/core/theme/tokens.stylex";
12
- import { Token } from "@astryxdesign/core/Token";
13
+ import { Token, type TokenProps } from "@astryxdesign/core/Token";
13
14
  import { Tooltip } from "@astryxdesign/core/Tooltip";
14
15
  import * as stylex from "@stylexjs/stylex";
15
16
  import type { ReactNode } from "react";
@@ -17,9 +18,7 @@ import type { ReactNode } from "react";
17
18
  import { RemoveButton } from "./RemoveButton";
18
19
 
19
20
  /** What `Chip` takes. */
20
- export type ChipProps = {
21
- /** The chip's text; cut short with an ellipsis past `maxWidth`, and shown whole in a tooltip. */
22
- readonly label: string;
21
+ export type ChipProps = Pick<TokenProps, "label" | "isDisabled"> & {
23
22
  /** `neutral` (default) is the plain grey chip; `accent` the pale accent one; `danger` a value no longer valid. */
24
23
  readonly tone?: "neutral" | "accent" | "danger" | undefined;
25
24
  /** The chip's height, `sm` or `md` (default), as the controls beside it. */
@@ -40,8 +39,6 @@ export type ChipProps = {
40
39
  * button over its end; it stays in the tab order. Without it the button always shows after the text.
41
40
  */
42
41
  readonly isRemoveOnHover?: boolean | undefined;
43
- /** Dims the chip and blocks its remove button. */
44
- readonly isDisabled?: boolean | undefined;
45
42
  /** Read-only: no remove button. */
46
43
  readonly isReadOnly?: boolean | undefined;
47
44
  };
@@ -102,8 +99,9 @@ export function Chip(props: ChipProps): ReactNode {
102
99
  // The remove button shows while the chip is hovered or has focus inside it.
103
100
  const marker = stylex.defaultMarker();
104
101
 
102
+ const accentFill: AccentRole = "var(--ants-accent-fill)";
105
103
  const styles = stylex.create({
106
- // fe-app's chip (ItemPreviewSource): a pill as high as the controls, 10px either side (no
104
+ // A pill as high as the controls, 10px either side (no
107
105
  // spacing step is 10px), in 12px body text of the normal weight. An xstyle, not a `token` theme entry: Token is also
108
106
  // drawn by ChipListAddPopover's unframed list in Astryx's own look, which a theme entry would change.
109
107
  chip: {
@@ -117,10 +115,10 @@ const styles = stylex.create({
117
115
  md: { height: sizeVars["--size-element-md"] },
118
116
  // Not `color="blue"` with a theme entry, for the same reason.
119
117
  accent: {
120
- backgroundColor: "var(--ants-mh-blue-200)",
118
+ backgroundColor: accentFill,
121
119
  color: colorVars["--color-text-primary"],
122
120
  },
123
- // fe-app's: a 16px white circle over the chip's end (Token's own remove button is 16px too).
121
+ // A 16px white circle over the chip's end (Token's own remove button is 16px too).
124
122
  hoverRemove: {
125
123
  position: "absolute",
126
124
  insetInlineEnd: spacingVars["--spacing-1"],
@@ -16,8 +16,8 @@ export type InfoTipProps = {
16
16
  export function InfoTip({ label, tone = "info" }: InfoTipProps): ReactNode {
17
17
  const isError = tone === "error";
18
18
  return (
19
- <Tooltip content={label}>
20
- <Text as="span" color="accent" aria-label={label} xstyle={styles.info}>
19
+ <Tooltip content={label} focusTrigger="always">
20
+ <Text as="span" tabIndex={0} color="accent" aria-label={label} xstyle={styles.info}>
21
21
  <Icon icon={isError ? "warning" : "info"} size="xsm" {...(isError && { color: "error" })} />
22
22
  </Text>
23
23
  </Tooltip>
@@ -7,7 +7,7 @@ import type { ReactNode } from "react";
7
7
  export type LabeledDividerProps = {
8
8
  /** The short word on the rule, such as "or"; it also names the separator. */
9
9
  readonly label: string;
10
- /** `sentence` (default) shows the word as given; `upper` in capitals, as fe-app's "OR". */
10
+ /** `sentence` (default) shows the word as given; `upper` in capitals, such as "OR". */
11
11
  readonly case?: "sentence" | "upper" | undefined;
12
12
  };
13
13
 
@@ -28,7 +28,7 @@ export type PanelSearchInputProps = {
28
28
  /** The clear button's name, such as "Clear search"; it shows only an icon. */
29
29
  readonly clear: string;
30
30
  };
31
- /** Where the magnifier sits: `start` (default) or `end`, after the clear button, as fe-app's search. */
31
+ /** Where the magnifier sits: `start` (default) or `end`, after the clear button. */
32
32
  readonly iconPosition?: "start" | "end" | undefined;
33
33
  /** The input element, to focus it (with `useFocusLater` in a pop-over, say). */
34
34
  readonly ref?: Ref<HTMLInputElement> | undefined;
@@ -1,15 +1,11 @@
1
1
  import { Icon } from "@astryxdesign/core/Icon";
2
- import { IconButton } from "@astryxdesign/core/IconButton";
2
+ import { IconButton, type IconButtonProps } from "@astryxdesign/core/IconButton";
3
3
  import type { ReactNode } from "react";
4
4
 
5
5
  /** What `RemoveButton` takes. */
6
- export type RemoveButtonProps = {
7
- /** What goes, such as "Remove row"; the button shows only an icon, so this is its name. */
8
- readonly label: string;
6
+ export type RemoveButtonProps = Pick<IconButtonProps, "label" | "isDisabled"> & {
9
7
  /** The button's height, `md` by default. */
10
8
  readonly size?: "sm" | "md" | undefined;
11
- /** Dimmed and not pressable. */
12
- readonly isDisabled?: boolean | undefined;
13
9
  /** Read-only: a remove is an editing affordance, so nothing is drawn. */
14
10
  readonly isReadOnly?: boolean | undefined;
15
11
  /** Called when the button is pressed. */
@@ -1,16 +1,16 @@
1
1
  import { Icon } from "@astryxdesign/core/Icon";
2
- import { Link } from "@astryxdesign/core/Link";
2
+ import { Link, type LinkProps } from "@astryxdesign/core/Link";
3
3
  import { spacingVars } from "@astryxdesign/core/theme/tokens.stylex";
4
4
  import * as stylex from "@stylexjs/stylex";
5
5
  import type { ReactNode } from "react";
6
6
 
7
7
  /** What `TextAction` takes. */
8
- export type TextActionProps = {
8
+ export type TextActionProps = Pick<LinkProps, "isDisabled"> & {
9
9
  /** What it does, such as "Remove all"; shown in capitals by default, so pass it in normal case. */
10
10
  readonly label: string;
11
11
  /** A glyph before the text, in the text's colour, such as an Astryx `Icon` of a bin for a removal. */
12
12
  readonly icon?: ReactNode;
13
- /** `upper` (default) shows the label in capitals, as fe-app's "REMOVE ALL"; `sentence` as given, as fe-app's text buttons. */
13
+ /** `upper` (default) shows the label in capitals, such as "REMOVE ALL"; `sentence` as given. */
14
14
  readonly case?: "upper" | "sentence" | undefined;
15
15
  /**
16
16
  * Called when it is pressed. Leave it out when a wrapper handles the presses, such as an Astryx
@@ -19,13 +19,11 @@ export type TextActionProps = {
19
19
  readonly onClick?: (() => void) | undefined;
20
20
  /** A down chevron after the text, for an action that opens a menu. */
21
21
  readonly hasChevron?: boolean | undefined;
22
- /** Dimmed and not pressable. */
23
- readonly isDisabled?: boolean | undefined;
24
22
  };
25
23
 
26
24
  /**
27
- * An action drawn as text alone: bold capitals in the accent, no frame, as fe-app's "REMOVE ALL"
28
- * and "CREATE NEW … ▾", or in sentence case with a glyph before it, as fe-app's text buttons
25
+ * An action drawn as text alone: bold capitals in the accent, no frame, such as "REMOVE ALL"
26
+ * and "CREATE NEW … ▾", or in sentence case with a glyph before it
29
27
  * (a bin, then "Remove"). An Astryx `Link` without an `href`, so a `<button>` in a link's look, with
30
28
  * a down chevron after the text when it opens a menu; still a button without `onClick`, so it can
31
29
  * be a `Popover`'s trigger. It holds no state.
@@ -54,7 +52,7 @@ export function TextAction({
54
52
  }
55
53
 
56
54
  const styles = stylex.create({
57
- // Capitals are a look, not text, so translations stay in normal case. fe-app's text actions have
55
+ // Capitals are a look, not text, so translations stay in normal case. Text actions have
58
56
  // no underline, not even on hover (Astryx's Link adds one). The theme styles no link, so this
59
57
  // sets what nothing else does.
60
58
  action: { whiteSpace: "nowrap", textDecoration: "none" },
@@ -11,7 +11,7 @@ export type TruncatedOptionProps = {
11
11
 
12
12
  /**
13
13
  * A Selector option whose label ends in an ellipsis when it is wider than the pop-up, with the
14
- * whole label in a tooltip on hover, as antd's Select. Its icon and description show as the
14
+ * whole label in a tooltip on hover. Its icon and description show as the
15
15
  * Selector's own option's. It holds no state.
16
16
  */
17
17
  export function TruncatedOption({ option }: TruncatedOptionProps): ReactNode {
@@ -0,0 +1,67 @@
1
+ import { z } from "zod";
2
+
3
+ const text = z.string().trim().min(1);
4
+
5
+ /** Structured guidance beside a view; its props and examples come from TypeScript and stories. */
6
+ export const componentDocSchema = z.object({
7
+ category: z
8
+ .enum(["Inputs", "Selection", "Feedback", "Layout", "Data display", "Overlays", "Theme"])
9
+ .describe(
10
+ "The view's purpose: Inputs edits values; Selection chooses options; Feedback communicates status; Layout arranges content; Data display presents values; Overlays shows dialogs or panels; Theme provides visual tokens and icons.",
11
+ ),
12
+ keywords: z.array(text).min(3).describe("At least three search terms for the view."),
13
+ related: z
14
+ .array(
15
+ z.object({
16
+ name: text.describe("A kit view or Astryx facade module, such as astryx/Badge."),
17
+ when: text.describe("When to choose this alternative."),
18
+ }),
19
+ )
20
+ .min(1)
21
+ .optional()
22
+ .describe(
23
+ "Alternatives, validated against public views and facade modules during docs generation.",
24
+ ),
25
+ example: text
26
+ .optional()
27
+ .describe("The existing story export shown first, validated during docs generation."),
28
+ usage: z
29
+ .object({
30
+ description: text.describe("When and how to use the view."),
31
+ bestPractices: z
32
+ .array(
33
+ z.object({
34
+ guidance: z
35
+ .boolean()
36
+ .describe("True for a recommended practice; false for a practice to avoid."),
37
+ description: text.describe("The practice and its reason."),
38
+ }),
39
+ )
40
+ .min(2)
41
+ .refine(
42
+ (items) => items.some((item) => item.guidance) && items.some((item) => !item.guidance),
43
+ "Include a recommended practice and a practice to avoid.",
44
+ )
45
+ .describe("Recommended practices and practices to avoid."),
46
+ anatomy: z
47
+ .array(
48
+ z.object({
49
+ name: text.describe("The part's name."),
50
+ required: z.boolean().describe("Whether this part is always present."),
51
+ description: text.describe("The part's purpose and the condition when it appears."),
52
+ }),
53
+ )
54
+ .min(1)
55
+ .optional()
56
+ .describe("The view's visible and interactive parts."),
57
+ })
58
+ .describe("Usage, best practices and optional anatomy."),
59
+ accessibility: z
60
+ .array(text)
61
+ .min(1)
62
+ .optional()
63
+ .describe("Accessible naming and interaction guidance."),
64
+ });
65
+
66
+ /** Authoring contract for a view's `<View>.doc.ts` export. */
67
+ export type ComponentDoc = z.input<typeof componentDocSchema>;
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export { PathMark, type PathMarkProps, useEnclosingPath } from "./atoms/PathMark
15
15
  export { RowSkeletons, type RowSkeletonsProps } from "./atoms/RowSkeletons";
16
16
  export { TextAction, type TextActionProps } from "./atoms/TextAction";
17
17
  export { TruncatedText, type TruncatedTextProps } from "./atoms/TruncatedText";
18
+ export { TruncatedOption, type TruncatedOptionProps } from "./atoms/TruncatedOption";
18
19
  export { AccentCard, type AccentCardProps } from "./molecules/AccentCard";
19
20
  export { ChoiceDialog, type ChoiceDialogProps } from "./molecules/ChoiceDialog";
20
21
  export { type ChoiceTile, ChoiceTiles, type ChoiceTilesProps } from "./molecules/ChoiceTiles";
@@ -117,3 +118,4 @@ export { HourOfDayRow, type HourOfDayRowProps } from "./organisms/HourOfDayRow";
117
118
  export { HourOfWeekGrid, type HourOfWeekGridProps } from "./organisms/HourOfWeekGrid";
118
119
  export { ResizableDrawer, type ResizableDrawerProps } from "./organisms/ResizableDrawer";
119
120
  export { TreeSelect, type TreeSelectItem, type TreeSelectProps } from "./organisms/TreeSelect";
121
+ export { TreeSearchPanel, type TreeSearchPanelProps } from "./organisms/TreeSearchPanel";
package/src/lib/iso.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  createISOTimeString,
3
3
  type ISODateString,
4
4
  type ISOTimeString,
5
+ parseISO,
5
6
  } from "@astryxdesign/core/utils";
6
7
 
7
8
  // Astryx date and time inputs take a value only when it is a valid one; a draft's empty or broken
@@ -13,14 +14,25 @@ export function timeValue(time: string): { readonly value?: ISOTimeString } {
13
14
  return value === null ? {} : { value };
14
15
  }
15
16
 
16
- const isISODate = (value: string): value is ISODateString => /^\d{4}-\d{2}-\d{2}$/u.test(value);
17
+ const hasISODateShape = (value: string): value is ISODateString =>
18
+ /^\d{4}-\d{2}-\d{2}$/u.test(value);
17
19
 
18
- /** Spread into a date input: `{ value }` when `date` is `YYYY-MM-DD`, `{}` otherwise. */
20
+ function isISODate(value: string): value is ISODateString {
21
+ if (!hasISODateShape(value)) return false;
22
+ try {
23
+ parseISO(value);
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ /** Spread into a date input: `{ value }` for a valid calendar date in `YYYY-MM-DD`, `{}` otherwise. */
19
31
  export function dateValue(date: string): { readonly value?: ISODateString } {
20
32
  return isISODate(date) ? { value: date } : {};
21
33
  }
22
34
 
23
- /** Spread into a date input: `{ min }` when `min` is `YYYY-MM-DD`, `{}` otherwise. */
35
+ /** Spread into a date input: `{ min }` for a valid calendar date in `YYYY-MM-DD`, `{}` otherwise. */
24
36
  export function minDate(min: string | undefined): { readonly min?: ISODateString } {
25
37
  return min !== undefined && isISODate(min) ? { min } : {};
26
38
  }
@@ -255,7 +255,7 @@ const theme = EditorView.theme({
255
255
  font: "inherit",
256
256
  fontSize: "12px",
257
257
  borderRadius: "50%",
258
- color: "var(--ants-mh-blue-700)",
258
+ color: "var(--color-icon-accent)",
259
259
  cursor: "pointer",
260
260
  },
261
261
  ".cm-token:hover .cm-token-remove, .cm-token:focus-within .cm-token-remove": { opacity: "1" },
@@ -14,8 +14,7 @@ export type LoadMoreOptions = {
14
14
  readonly itemCount: number;
15
15
  };
16
16
 
17
- // fe-app loads the next page when its list is scrolled this close to the end
18
- // (UITargetUpdateSegment `index.jsx:719-728`).
17
+ // Load the next page 20px before the list's end so scrolling can continue without a pause.
19
18
  const NEAR_END = "0px 0px 20px 0px";
20
19
 
21
20
  /**
@@ -1,7 +1,7 @@
1
1
  import { useSpinDelay } from "spin-delay";
2
2
 
3
3
  /**
4
- * When a loading state shows, for every view of the kit (marketinghub ADR 0080): only after the load has
4
+ * When a loading state shows, for every view of the kit: only after the load has
5
5
  * lasted `delay` ms, then for at least `minDuration` ms, so a fast load shows nothing and a slow
6
6
  * one never flashes. `delay` matches the antsomi theme's skeleton `animationDelay`.
7
7
  */
@@ -1,3 +1,4 @@
1
+ import type { AccentRole } from "@antscorp/astryx-theme-antsomi/palette";
1
2
  import { Card } from "@astryxdesign/core/Card";
2
3
  import { HStack } from "@astryxdesign/core/HStack";
3
4
  import { Icon } from "@astryxdesign/core/Icon";
@@ -39,9 +40,8 @@ export type AccentCardProps = {
39
40
  readonly isDisabled?: boolean | undefined;
40
41
  /**
41
42
  * `stripe` (default): a grey border with an accent stripe down the start edge and a light
42
- * header bar, as fe-app's Perform event card. `banner`: no border nor stripe but a soft shadow,
43
- * under a dark blue header bar with the title in white capitals and a white close cross, as
44
- * fe-app's audience picker.
43
+ * header bar. `banner`: no border nor stripe but a soft shadow,
44
+ * under a dark blue header bar with the title in white capitals and a white close cross.
45
45
  */
46
46
  readonly variant?: "stripe" | "banner" | undefined;
47
47
  };
@@ -87,7 +87,7 @@ export function AccentCard(props: AccentCardProps): ReactNode {
87
87
  <RemoveButton
88
88
  label={close}
89
89
  size="sm"
90
- isDisabled={props.isDisabled}
90
+ isDisabled={props.isDisabled ?? false}
91
91
  onClick={props.onClose}
92
92
  />
93
93
  ))}
@@ -102,8 +102,9 @@ export function AccentCard(props: AccentCardProps): ReactNode {
102
102
  );
103
103
  }
104
104
 
105
+ const accentBanner: AccentRole = "var(--ants-accent-banner)";
105
106
  const styles = stylex.create({
106
- // fe-app's Perform event card (UIPerformEventV2 GeneralContainerCondition): a grey border and a
107
+ // A grey border and a
107
108
  // 4px accent stripe at the start (no border token is 4px). A look of this card only, which no
108
109
  // Card prop nor theme entry (it would paint every card) can express; `padding={0}` clears the rest.
109
110
  card: {
@@ -111,19 +112,19 @@ const styles = stylex.create({
111
112
  borderInlineStartWidth: 4,
112
113
  borderInlineStartColor: colorVars["--color-accent"],
113
114
  },
114
- // fe-app's header bar: 10px above and below, between the spacing steps of 8px and 12px, and as
115
+ // The header bar: 10px above and below, between the spacing steps of 8px and 12px, and as
115
116
  // high with the close button (sm) as without it.
116
117
  header: {
117
118
  paddingBlock: 10,
118
119
  minHeight: `calc(${sizeVars["--size-element-sm"]} + ${spacingVars["--spacing-5"]})`,
119
120
  backgroundColor: "var(--ants-mh-gray-75)",
120
121
  },
121
- // fe-app's audience picker (UITargetSelection ContainerTargetAudience): no border, square
122
- // corners and a soft shadow (Astryx's low elevation, near fe-app's 0 1px 4px grey).
122
+ // The banner card: no border, square
123
+ // corners and a soft shadow (Astryx's low elevation, near a 0 1px 4px grey shadow).
123
124
  bannerCard: { borderStyle: "none", borderRadius: 0 },
124
- // fe-app's HeaderTargetAudience: white semibold capitals on its header blue, 10px 20px around.
125
+ // White semibold capitals on the header blue, 10px 20px around.
125
126
  banner: {
126
- backgroundColor: "var(--ants-mh-blue-750)",
127
+ backgroundColor: accentBanner,
127
128
  color: colorVars["--color-on-accent"],
128
129
  textTransform: "uppercase",
129
130
  },
@@ -1,4 +1,4 @@
1
- import { CheckboxInput } from "@astryxdesign/core/CheckboxInput";
1
+ import { CheckboxInput, type CheckboxInputProps } from "@astryxdesign/core/CheckboxInput";
2
2
  import { HStack } from "@astryxdesign/core/HStack";
3
3
  import { Text } from "@astryxdesign/core/Text";
4
4
  import { sizeVars } from "@astryxdesign/core/theme/tokens.stylex";
@@ -10,9 +10,7 @@ import { HoverSwap } from "../atoms/HoverSwap";
10
10
  import { TruncatedText } from "../atoms/TruncatedText";
11
11
 
12
12
  /** What `CheckRow` takes. */
13
- export type CheckRowProps = {
14
- /** The row's text and the checkbox's name; one line, the whole text in a tooltip when cut. */
15
- readonly label: string;
13
+ export type CheckRowProps = Pick<CheckboxInputProps, "label" | "isDisabled" | "isReadOnly"> & {
16
14
  /** A second line under the text, such as an id under a name; one line too. */
17
15
  readonly description?: string | undefined;
18
16
  /** Whether the box is ticked. */
@@ -26,17 +24,13 @@ export type CheckRowProps = {
26
24
  * link to the item; the two share one place, so nothing moves. None without it.
27
25
  */
28
26
  readonly hoverContent?: ReactNode;
29
- /** Dims the box and blocks it; nothing is hidden. */
30
- readonly isDisabled?: boolean | undefined;
31
- /** Shows the box's state, not dimmed, and blocks it. */
32
- readonly isReadOnly?: boolean | undefined;
33
27
  };
34
28
 
35
29
  /**
36
30
  * One row of a list to tick: a checkbox (md, as every checkbox), the row's text on one line (and
37
31
  * an optional second line), and content at its end that `hoverContent` replaces while the row is
38
32
  * hovered or focused. The box is the control and is named by the text; the text itself is not a
39
- * click target, as in fe-app. As high as a small control, so `RowSkeletons variant="check"`
33
+ * click target. As high as a small control, so `RowSkeletons variant="check"`
40
34
  * stands in for it without a shift. It holds no state.
41
35
  */
42
36
  export function CheckRow(props: CheckRowProps): ReactNode {
@@ -1,3 +1,4 @@
1
+ import type { AccentRole } from "@antscorp/astryx-theme-antsomi/palette";
1
2
  import { Center } from "@astryxdesign/core/Center";
2
3
  import { ClickableCard } from "@astryxdesign/core/ClickableCard";
3
4
  import { HStack } from "@astryxdesign/core/HStack";
@@ -32,7 +33,7 @@ export type ChoiceTilesProps<V extends string> = {
32
33
  };
33
34
 
34
35
  /**
35
- * A choice among a few ways to go on, as antsomi-ui's tiles: a wrapping row of 180×170 cards, each
36
+ * A choice among a few ways to go on: a wrapping row of 180×170 cards, each
36
37
  * a button with its glyph in a soft accent circle, a bold title and a description under it. A pick
37
38
  * is an action, not a selected state: the host shows what follows instead. It holds no state.
38
39
  */
@@ -64,19 +65,20 @@ export function ChoiceTiles<V extends string>(props: ChoiceTilesProps<V>): React
64
65
  );
65
66
  }
66
67
 
67
- // fe-app's tiles are 150×130 at its 12px text; ours is larger, so a two-line description fits here.
68
+ // A 150×130 tile at 12px text is too tight; 180×170 leaves room for a two-line description.
68
69
  const TILE = { width: 180, height: 170 } as const;
69
70
 
71
+ const accentHover: AccentRole = "var(--ants-accent-hover)";
70
72
  const styles = stylex.create({
71
73
  // Text has no alignment prop: its lines centre under the glyph from here.
72
74
  fill: { height: "100%", textAlign: "center" },
73
- // antsomi-ui's tile glyph: a 50px circle in the accent's lightest tint, the glyph in the accent.
75
+ // A 50px circle in the accent's lightest tint, the glyph in the accent.
74
76
  circle: {
75
77
  flexShrink: 0,
76
78
  width: 50,
77
79
  height: 50,
78
80
  borderRadius: "50%",
79
- backgroundColor: "var(--ants-mh-blue-50)",
81
+ backgroundColor: accentHover,
80
82
  color: colorVars["--color-accent"],
81
83
  },
82
84
  });