@antadesign/anta 0.3.12 → 0.3.13
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/README.md +31 -36
- package/dist/anta_helpers.js +5 -0
- package/dist/components/Button.d.ts +17 -4
- package/dist/components/Button.js +8 -0
- package/dist/components/InputDate.d.ts +9 -1
- package/dist/components/InputDate.js +4 -1
- package/dist/components/Menu.d.ts +6 -5
- package/dist/components/Select.d.ts +63 -51
- package/dist/components/Select.js +9 -8
- package/dist/components/SelectFaceted.d.ts +21 -16
- package/dist/components/SelectFaceted.js +2 -2
- package/dist/components/Text.d.ts +2 -2
- package/dist/components/Title.d.ts +2 -2
- package/dist/components/Tooltip.d.ts +1 -1
- package/dist/components/select-options.d.ts +10 -5
- package/dist/components/select-options.js +1 -1
- package/dist/elements/a-button.css +1 -1
- package/dist/elements/a-checkbox.css +1 -1
- package/dist/elements/a-expander.css +1 -1
- package/dist/elements/a-expander.js +1 -0
- package/dist/elements/a-input-time.css +1 -1
- package/dist/elements/a-input.css +1 -1
- package/dist/elements/a-input.js +2 -2
- package/dist/elements/a-menu-item.css +1 -1
- package/dist/elements/a-menu.d.ts +6 -2
- package/dist/elements/a-menu.js +65 -28
- package/dist/elements/a-radio-group.css +1 -1
- package/dist/elements/a-radio-group.d.ts +1 -0
- package/dist/elements/a-radio-group.js +11 -2
- package/dist/elements/a-radio.css +1 -1
- package/dist/elements/a-tabs.css +1 -1
- package/dist/elements/a-tabs.d.ts +1 -0
- package/dist/elements/a-tabs.js +35 -6
- package/dist/elements/a-tag.css +1 -1
- package/dist/elements/a-text.css +1 -1
- package/dist/elements/a-title.css +1 -1
- package/dist/elements/a-tooltip.js +1 -1
- package/dist/general_types.d.ts +12 -9
- package/dist/index.d.ts +1 -1
- package/dist/reset.css +1 -1
- package/dist/theme-anta.css +1 -0
- package/dist/tokens.css +1 -1
- package/package.json +2 -1
- package/dist/components/Card.module.css +0 -35
- package/dist/components/Progress.module.css +0 -0
- package/dist/components/Radio.d.ts +0 -37
- package/dist/components/Radio.js +0 -33
- package/dist/components/Tab.d.ts +0 -37
- package/dist/components/Tab.js +0 -5
- package/dist/components/Tabs.module.css +0 -1
- package/dist/components/Text.module.css +0 -0
package/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# @antadesign/anta
|
|
2
2
|
|
|
3
|
-
<a href="https://antithesis.com" target="_blank" rel="noopener noreferrer">Antithesis</a> design system, **Anta**, has three layers: global CSS tokens, framework-agnostic web components that work
|
|
3
|
+
<a href="https://antithesis.com" target="_blank" rel="noopener noreferrer">Antithesis</a> design system, **Anta**, has three layers: global CSS tokens with swappable themes, framework-agnostic web components that work in plain HTML, and JSX wrappers for React and Preact.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
This constraint shapes the architecture: the web components carry the core styling and the occasional browser-API call (e.g. `getBoundingClientRect()`) without imposing a framework. Majority of web components are stateless. The React/Preact wrappers exist for dynamic state and conditional rendering.
|
|
5
|
+
One constraint shapes the architecture. Antithesis runs most of its UI in a Worker thread, driven by a custom reactive engine, and a component that mutates its own attributes would desync the Worker from the UI thread. So Anta's web components are **fully declarative**: they never touch their own attributes, and internal state lives in Shadow DOM, invisible to the outer document. The components carry the styling without imposing a framework, and most are stateless; the JSX wrappers exist for dynamic state and conditional rendering.
|
|
8
6
|
|
|
9
7
|
## Installation
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
```sh
|
|
10
|
+
npm install @antadesign/anta # or pnpm / bun
|
|
11
|
+
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Pin an exact version in your `package.json` (`"@antadesign/anta": "0.3.12"`) rather than a floating tag like `"latest"`, which can change between installs.
|
|
14
14
|
|
|
15
15
|
### Usage
|
|
16
16
|
|
|
@@ -25,24 +25,23 @@ import { Progress } from '@antadesign/anta'
|
|
|
25
25
|
|
|
26
26
|
### What you import (and why)
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Five imports, listed in the order you'd add them. Tokens + elements + the JSX layer render a styled component; the reset is recommended; the theme is opt-in.
|
|
29
29
|
|
|
30
30
|
| Import | Provides | Skip if… |
|
|
31
31
|
|---|---|---|
|
|
32
|
-
| `@antadesign/anta/tokens.css` | The CSS custom properties
|
|
33
|
-
| `@antadesign/anta/reset.css` |
|
|
34
|
-
| `@antadesign/anta/elements` | Side-effect import
|
|
35
|
-
| `@antadesign/anta` | The JSX wrappers (`Progress`, `Text`, `Icon
|
|
32
|
+
| `@antadesign/anta/tokens.css` | The CSS custom properties: six tone seeds (`--anta-seed-neutral`, `--anta-seed-brand`, …), the role scales derived from them (`--bg-1…5`, `--text-1…5`, `--border-1…5`), the `.dark` ancestor toggle, and the base `font-size: 15px`. Also declares the `@layer base, anta, components, utilities` order. Override a seed to reskin its whole tone. | You're supplying your own values for the same variables. |
|
|
33
|
+
| `@antadesign/anta/reset.css` | Small modern reset (box-sizing, margins, form-control font inheritance) plus Anta's typography for headings, lists, and links. Lives in `@layer anta`. | You already have a reset and don't want Anta's typography defaults. |
|
|
34
|
+
| `@antadesign/anta/elements` | Side-effect import: registers every `<a-*>` element and attaches its CSS (also `@layer anta`). Per-element entries register just one — see [Registering elements](#registering-elements). | You render only on the server, or you import per-element entries instead. |
|
|
35
|
+
| `@antadesign/anta` | The JSX wrappers (`Progress`, `Text`, `Icon`, …): typed React/Preact components that emit `<a-*>` tags. | You write the `<a-*>` elements by hand. |
|
|
36
|
+
| `@antadesign/anta/theme-anta.css` *(optional)* | Anta's hand-tuned reference palette. The default palette is seed-derived; import this last for the exact hand-tuned values. | The seed-derived default, or your own palette, is what you want. |
|
|
36
37
|
|
|
37
|
-
The
|
|
38
|
-
|
|
39
|
-
`@antadesign/anta/elements` registers **all** elements — convenient, but it includes every element's code (and deps). To keep your bundle lean, import only the elements you use from their per-element entry points instead: `import '@antadesign/anta/elements/a-tooltip'` registers just `<a-tooltip>` **and loads just its CSS**. Unused elements — and any dependencies they pull in — then never enter your bundle. See [Registering elements](#registering-elements).
|
|
38
|
+
The order matters at the top: the element CSS reads `var(--text-1)`, `var(--bg-2)`, and friends, and only `tokens.css` defines them. Skip the tokens import and components render unstyled.
|
|
40
39
|
|
|
41
40
|
### Cascade layers
|
|
42
41
|
|
|
43
|
-
Anta's reset and element CSS live in `@layer anta`. `tokens.css` pre-declares
|
|
42
|
+
Anta's reset and element CSS live in `@layer anta`. `tokens.css` pre-declares `@layer base, anta, components, utilities;`: Anta's defaults sit above preflight resets (Tailwind's `@layer base`, Normalize) and below your `@layer components` rules and utility frameworks.
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
For a different order, declare it in your own CSS loaded **before** `tokens.css` — the first mention of each layer name fixes its position:
|
|
46
45
|
|
|
47
46
|
```css
|
|
48
47
|
/* your global.css, loaded before anta */
|
|
@@ -51,37 +50,33 @@ If you need a different order, declare it in your *own* CSS file that loads **be
|
|
|
51
50
|
|
|
52
51
|
CSS custom properties (the `:root { --… }` declarations in `tokens.css`) stay unlayered so they take effect everywhere unconditionally.
|
|
53
52
|
|
|
54
|
-
> **Gotcha: unlayered hard
|
|
55
|
-
>
|
|
56
|
-
> A snippet like this in your global CSS — common copy-paste from older reset guides — silently overrides every element rule Anta ships:
|
|
53
|
+
> **Gotcha: an unlayered hard reset defeats Anta's element rules.**
|
|
57
54
|
>
|
|
58
55
|
> ```css
|
|
59
56
|
> *, *::before, *::after { box-sizing: border-box; }
|
|
60
57
|
> * { margin: 0; }
|
|
61
58
|
> ```
|
|
62
59
|
>
|
|
63
|
-
> Unlayered styles
|
|
64
|
-
>
|
|
65
|
-
> If you're importing `@antadesign/anta/reset.css`, Anta already does the same universal `box-sizing` and margin reset, *inside* `@layer anta`. The element-level rules sitting on top are intentionally polite defaults — sensible out of the box, trivially overridable when you want something else (any rule in `@layer components` / `@layer utilities`, or any unlayered rule of your own that targets specific elements, wins automatically). **Delete the duplicate hard reset from your global CSS** so those defaults can apply. If you want to keep your own reset, wrap it in `@layer base { … }` so it sits below `anta` in the declared order and Anta's rules still win element-by-element.
|
|
60
|
+
> Unlayered styles beat layered ones regardless of specificity, so this common copy-paste outranks every per-element default Anta ships (`p` margins, list padding, and the rest). `reset.css` already does the same universal reset inside `@layer anta` — delete the duplicate from your global CSS, or wrap your own reset in `@layer base { … }` so Anta's element rules still win.
|
|
66
61
|
|
|
67
62
|
## Registering elements
|
|
68
63
|
|
|
69
|
-
The JSX wrappers
|
|
64
|
+
The JSX wrappers render `<a-*>` tags, and the classes behind those tags must be registered **before** they appear in the DOM. Registration needs `HTMLElement`, which exists only in the UI thread of a real browser; in Node.js (SSR) and Worker threads the import is a silent no-op that costs nothing but bundle size.
|
|
70
65
|
|
|
71
66
|
```ts
|
|
72
67
|
import '@antadesign/anta/elements' // auto-registers all elements
|
|
73
68
|
```
|
|
74
69
|
|
|
75
|
-
**Register only what you use.** `/elements`
|
|
70
|
+
**Register only what you use.** `/elements` registers everything; per-element entry points register one element and load just its CSS, keeping unused elements (and their dependencies) out of your bundle:
|
|
76
71
|
|
|
77
72
|
```ts
|
|
78
73
|
import '@antadesign/anta/elements/a-tooltip' // only <a-tooltip> + its CSS
|
|
79
74
|
import '@antadesign/anta/elements/a-button' // only <a-button> + its CSS
|
|
80
75
|
```
|
|
81
76
|
|
|
82
|
-
Both
|
|
77
|
+
Both forms are side-effect imports, idempotent, and SSR-safe.
|
|
83
78
|
|
|
84
|
-
The cleanest pattern is a
|
|
79
|
+
The cleanest pattern is a static import at your app's entry file, outside any component or hook:
|
|
85
80
|
|
|
86
81
|
```ts
|
|
87
82
|
// src/main.tsx (or wherever your root render lives)
|
|
@@ -91,19 +86,18 @@ import App from './App'
|
|
|
91
86
|
createRoot(document.getElementById('root')!).render(<App />)
|
|
92
87
|
```
|
|
93
88
|
|
|
94
|
-
Bundlers resolve this at module-init time, so
|
|
89
|
+
Bundlers resolve this at module-init time, so the element classes are registered before anything renders: no flash of un-upgraded elements.
|
|
95
90
|
|
|
96
91
|
> **Why not `useEffect(() => import('@antadesign/anta/elements'), [])`?**
|
|
97
|
-
>
|
|
98
|
-
> `useEffect` fires after paint, and the dynamic `import()` itself is asynchronous. In practice the browser paints unregistered custom elements (which collapse to nothing) for a few hundred milliseconds before the upgrade catches up. `useLayoutEffect` doesn't help either: the async import still resolves after paint, and `useLayoutEffect` warns during SSR hydration. A static import at the entry file avoids all of this.
|
|
92
|
+
> `useEffect` fires after paint and the dynamic import resolves later still, so the browser paints unregistered elements (which collapse to nothing) until the upgrade catches up. `useLayoutEffect` doesn't help: the import is still async, and it warns during SSR hydration.
|
|
99
93
|
|
|
100
|
-
Where
|
|
94
|
+
Where the static import goes depends on the runtime:
|
|
101
95
|
|
|
102
|
-
**Plain HTML / static sites** —
|
|
96
|
+
**Plain HTML / static sites** — a `<script type="module">` tag in the document head.
|
|
103
97
|
|
|
104
|
-
**SSR frameworks (Astro, Next.js)** —
|
|
98
|
+
**SSR frameworks (Astro, Next.js)** — a script the framework ships only to the client. In Astro: `<script>import '@antadesign/anta/elements'</script>` (Astro `<script>` tags are client-side by default). In Next.js: a top-level import in a `'use client'` file, which only enters the client chunk.
|
|
105
99
|
|
|
106
|
-
**
|
|
100
|
+
**UI in a Worker thread (the Antithesis setup)** — the UI-thread bootstrap, the script that owns the real DOM. The Worker has no `HTMLElement`, so the import must not run there.
|
|
107
101
|
|
|
108
102
|
## Framework setup
|
|
109
103
|
|
|
@@ -137,7 +131,7 @@ If you only use the JSX wrappers (`<Button>`, `<Progress>`, …) you need no set
|
|
|
137
131
|
|
|
138
132
|
Every `a-*` tag type-checks, all standard HTML tags keep working, and importing anything from `@antadesign/stickers` adds the sticker tags automatically.
|
|
139
133
|
|
|
140
|
-
**Option B** — if you can't change `jsxImportSource` (a shared tsconfig, Emotion's `@emotion/react` source
|
|
134
|
+
**Option B** — if you can't change `jsxImportSource` (a shared tsconfig, Emotion's `@emotion/react` source), merge anta's tag map into your JSX namespace yourself: anta exports it as `AntaIntrinsicElements` (and `@antadesign/stickers` as `StickerIntrinsicElements`), one `extends` in any `.d.ts` your tsconfig covers:
|
|
141
135
|
|
|
142
136
|
```ts
|
|
143
137
|
import type { AntaIntrinsicElements } from '@antadesign/anta'
|
|
@@ -166,8 +160,9 @@ Either way the tags stay fully typed — unknown `a-*` tags and wrong prop types
|
|
|
166
160
|
|
|
167
161
|
### Raw web components (no JSX)
|
|
168
162
|
|
|
163
|
+
The elements work in plain HTML; registering them also loads their CSS. The bare specifier needs a bundler or an import map to resolve.
|
|
164
|
+
|
|
169
165
|
```html
|
|
170
|
-
<link rel="stylesheet" href="@antadesign/anta/elements/a-progress.css">
|
|
171
166
|
<script type="module">
|
|
172
167
|
import '@antadesign/anta/elements'
|
|
173
168
|
</script>
|
|
@@ -187,7 +182,7 @@ Add the `dark` class to any ancestor element:
|
|
|
187
182
|
|
|
188
183
|
## Fonts
|
|
189
184
|
|
|
190
|
-
Anta is designed
|
|
185
|
+
Anta is designed for a customized <a href="https://typetype.org/fonts/tt-interphases-pro" target="_blank" rel="noopener noreferrer">TT Interphases Pro</a> but ships no font binaries. Components reference families through the `--sans-serif` and `--monospace` variables and fall back to system stacks. The base size is `font-size: 15px` on `:root` (so `1rem = 15px`), a deliberate step down from the browser's 16px for Antithesis's information-dense layouts. Both live in `tokens.css`.
|
|
191
186
|
|
|
192
187
|
To use the Antithesis fonts, register your own `@font-face` declarations and override the variables:
|
|
193
188
|
|
package/dist/anta_helpers.js
CHANGED
|
@@ -86,6 +86,11 @@ class SelectableChildElement extends HTMLElementBase {
|
|
|
86
86
|
ariaProp = "ariaChecked";
|
|
87
87
|
internals = this.attachInternals?.();
|
|
88
88
|
connectedCallback() {
|
|
89
|
+
if (Object.prototype.hasOwnProperty.call(this, "selected")) {
|
|
90
|
+
const v = this.selected;
|
|
91
|
+
delete this.selected;
|
|
92
|
+
this.selected = v;
|
|
93
|
+
}
|
|
89
94
|
if (this.hasAttribute("selected")) this.applyState(true);
|
|
90
95
|
}
|
|
91
96
|
attributeChangedCallback(name) {
|
|
@@ -56,10 +56,23 @@ export type ContentMode = {
|
|
|
56
56
|
};
|
|
57
57
|
/** Submit axis — anchors (href) don't carry form-submission props; buttons
|
|
58
58
|
* don't carry anchor props. (Copy is no longer a Button concern — `ButtonCopy`
|
|
59
|
-
* composes a `<a-copy>` child; see `copy-props.ts`.)
|
|
59
|
+
* composes a `<a-copy>` child; see `copy-props.ts`.)
|
|
60
|
+
*
|
|
61
|
+
* `href` is **optional** so a conditional URL just works: `href={maybeUrl}`
|
|
62
|
+
* (`string | undefined`) type-checks, and a falsy href degrades to a plain
|
|
63
|
+
* button — a definite string renders `<a role="button">`, an absent / `undefined`
|
|
64
|
+
* href renders `<a-button>`, mirroring the `href != null` runtime check. Keeping
|
|
65
|
+
* it required forced a two-render (`href ? … : …`) or a conditional spread, and
|
|
66
|
+
* made TypeScript blame the wrong prop: with `href: string | undefined` matching
|
|
67
|
+
* neither branch, the intersection fell through to `PriorityMode` and reported a
|
|
68
|
+
* spurious "`priority` is missing". The anchor props (`href` / `target` / `rel` /
|
|
69
|
+
* `download` / `ping`) still can't combine with the button props (`type` /
|
|
70
|
+
* `form`). */
|
|
60
71
|
export type SubmitMode = {
|
|
61
|
-
/**
|
|
62
|
-
|
|
72
|
+
/** URL to link to. A definite string renders `<a role="button">`; omit it
|
|
73
|
+
* (or pass `undefined`) to render `<a-button>`, so `href={maybeUrl}`
|
|
74
|
+
* degrades to a plain button when the URL is absent. */
|
|
75
|
+
href?: string;
|
|
63
76
|
/** Anchor target. */
|
|
64
77
|
target?: string;
|
|
65
78
|
/** Anchor rel. */
|
|
@@ -122,4 +135,4 @@ export type ButtonProps = BaseButtonProps & PriorityMode & ContentMode & SubmitM
|
|
|
122
135
|
* <Button href="/docs" target="_blank" label="Read the docs" />
|
|
123
136
|
* ```
|
|
124
137
|
*/
|
|
125
|
-
export declare const Button: ({ priority, tone, underline, icon, iconTrailing, paddingless, label, size, loading, disabled, selected, round, href, type, form, className, style, children, ...rest }: ButtonProps) => any;
|
|
138
|
+
export declare const Button: ({ priority, tone, underline, icon, iconTrailing, paddingless, label, size, loading, disabled, selected, round, href, target, rel, download, ping, type, form, className, style, children, ...rest }: ButtonProps) => any;
|
|
@@ -14,6 +14,10 @@ const Button = ({
|
|
|
14
14
|
selected,
|
|
15
15
|
round,
|
|
16
16
|
href,
|
|
17
|
+
target,
|
|
18
|
+
rel,
|
|
19
|
+
download,
|
|
20
|
+
ping,
|
|
17
21
|
type,
|
|
18
22
|
form,
|
|
19
23
|
className,
|
|
@@ -72,6 +76,10 @@ const Button = ({
|
|
|
72
76
|
{
|
|
73
77
|
href,
|
|
74
78
|
"data-anta": "",
|
|
79
|
+
target,
|
|
80
|
+
rel,
|
|
81
|
+
download,
|
|
82
|
+
ping,
|
|
75
83
|
...sharedAttrs,
|
|
76
84
|
...rest,
|
|
77
85
|
children: inner
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BaseProps } from '../general_types';
|
|
2
2
|
import type { IconShape } from '../elements/a-icon.shapes';
|
|
3
|
+
import { type MenuProps } from './Menu';
|
|
3
4
|
/** Snapshot passed as the 2nd argument to `onValueChange` — the new ISO value
|
|
4
5
|
* (`''` when cleared) plus the field name (mirrors `Input` / `Calendar`). */
|
|
5
6
|
export interface InputDateChangeAttrs {
|
|
@@ -43,6 +44,13 @@ export interface InputDateProps extends Omit<BaseProps, 'children'> {
|
|
|
43
44
|
status?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'critical';
|
|
44
45
|
/** Round the field corners — `true` for fully round, or a number / CSS length. */
|
|
45
46
|
round?: boolean | number | string;
|
|
47
|
+
/** Preferred placement of the calendar menu relative to the field. Same values as
|
|
48
|
+
* `Menu`; it auto-flips and clamps when space runs out.
|
|
49
|
+
* @defaultValue bottom-start */
|
|
50
|
+
placement?: MenuProps['placement'];
|
|
51
|
+
/** Gap in pixels between the field and the calendar menu.
|
|
52
|
+
* @defaultValue 4 */
|
|
53
|
+
offset?: number;
|
|
46
54
|
/** Show a clear button once the field has a value. */
|
|
47
55
|
clearable?: boolean;
|
|
48
56
|
/** Leading icon at the start of the field — the calendar affordance. Pass
|
|
@@ -87,4 +95,4 @@ export interface InputDateProps extends Omit<BaseProps, 'children'> {
|
|
|
87
95
|
* <InputDate label="Starts" time defaultValue="2026-06-15T09:00" onValueChange={(v) => save(v)} />
|
|
88
96
|
* ```
|
|
89
97
|
*/
|
|
90
|
-
export declare const InputDate: ({ value, defaultValue, min, max, locale, name, size, disabled, label, hint, placeholder, status, round, clearable, icon, time, hour12, onValueChange, className, style, ...rest }: InputDateProps) => any;
|
|
98
|
+
export declare const InputDate: ({ value, defaultValue, min, max, locale, name, size, disabled, label, hint, placeholder, status, round, placement, offset, clearable, icon, time, hour12, onValueChange, className, style, ...rest }: InputDateProps) => any;
|
|
@@ -33,6 +33,8 @@ const InputDate = ({
|
|
|
33
33
|
placeholder,
|
|
34
34
|
status,
|
|
35
35
|
round,
|
|
36
|
+
placement,
|
|
37
|
+
offset,
|
|
36
38
|
clearable,
|
|
37
39
|
icon,
|
|
38
40
|
time,
|
|
@@ -154,7 +156,8 @@ const InputDate = ({
|
|
|
154
156
|
Menu,
|
|
155
157
|
{
|
|
156
158
|
open,
|
|
157
|
-
placement: "bottom-start",
|
|
159
|
+
placement: placement ?? "bottom-start",
|
|
160
|
+
offset,
|
|
158
161
|
autoWidth: true,
|
|
159
162
|
className: styles.calendarMenu,
|
|
160
163
|
onStateChange: (_e, { next }) => setOpen(next),
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { BaseProps } from '../general_types';
|
|
2
2
|
export interface MenuProps extends BaseProps {
|
|
3
|
-
/** Preferred placement relative to the trigger.
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
3
|
+
/** Preferred placement relative to the trigger. `bottom` / `top` place the menu
|
|
4
|
+
* below / above; `right` / `left` place it beside (like a submenu flyout). The
|
|
5
|
+
* cross-axis suffix (`-start` / `-end`) aligns the near / far edges, and no suffix
|
|
6
|
+
* centers on the trigger. The menu flips to the opposite side when the preferred
|
|
7
|
+
* one lacks room, and clamps to the viewport.
|
|
7
8
|
* @defaultValue bottom-start */
|
|
8
|
-
placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end' | 'bottom' | 'top';
|
|
9
|
+
placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end' | 'bottom' | 'top' | 'right-start' | 'right-end' | 'left-start' | 'left-end' | 'right' | 'left';
|
|
9
10
|
/** Open on right-click (the `contextmenu` event) of the trigger region
|
|
10
11
|
* instead of a left click, positioned at the pointer. */
|
|
11
12
|
context?: boolean;
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import type { BaseProps } from '../general_types';
|
|
2
2
|
import type { IconShape } from '../elements/a-icon.shapes';
|
|
3
3
|
import { type MenuProps } from './Menu';
|
|
4
|
+
/** The type an option `value` may take: `string`, `number`, or `boolean`. Selection
|
|
5
|
+
* compares values with `===`. Object-shaped values are out of scope; give the option a
|
|
6
|
+
* stable primitive `value` and read the object back off `attrs.option` (or, in
|
|
7
|
+
* `SelectFaceted`, use a `custom` facet). */
|
|
8
|
+
export type OptionValue = string | number | boolean;
|
|
4
9
|
/** One option in a `<Select>`. Pass a bare string as shorthand for
|
|
5
10
|
* `{ value: s, label: s }`. Carries an index signature so you can attach
|
|
6
11
|
* arbitrary fields (a `ranAt` date, a `status`, …) and read them back in
|
|
7
|
-
* `renderOption`; the built-in filter still matches on `value`/`label`/`hint`.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
* `renderOption`; the built-in filter still matches on `value`/`label`/`hint`.
|
|
13
|
+
*
|
|
14
|
+
* Generic in the value type `V` (defaults to `string`): `Select` infers `V` from your
|
|
15
|
+
* `options`, so `{ value: 365 }` round-trips as `number` through `onValueChange`. */
|
|
16
|
+
export interface SelectOption<V extends OptionValue = string> {
|
|
17
|
+
/** The option's value (`V`) — its identity, what `value` / `defaultValue` name, and
|
|
18
|
+
* what `onValueChange` reports. Unique across the whole `options` tree (selection is
|
|
11
19
|
* value-keyed and global across groups / submenus). */
|
|
12
|
-
value:
|
|
20
|
+
value: V;
|
|
13
21
|
/** Visible label. Defaults to `value`. */
|
|
14
22
|
label?: string;
|
|
15
23
|
/** Secondary text under the label (the option row's `hint`). */
|
|
@@ -32,11 +40,11 @@ export interface SelectOption {
|
|
|
32
40
|
/** A titled group of options rendered **inline** under a heading (like `MenuGroup`).
|
|
33
41
|
* Selection stays global — a group only organizes; its heading isn't selectable.
|
|
34
42
|
* `disabled` cascades to every descendant. Nest groups / submenus freely. */
|
|
35
|
-
export interface SelectGroup {
|
|
43
|
+
export interface SelectGroup<V extends OptionValue = string> {
|
|
36
44
|
/** The section heading (non-interactive). */
|
|
37
45
|
label: string;
|
|
38
46
|
/** The grouped items — options, or further groups / submenus. */
|
|
39
|
-
options: SelectItem[];
|
|
47
|
+
options: SelectItem<V>[];
|
|
40
48
|
/** Disable the whole group (cascades to all descendants). */
|
|
41
49
|
disabled?: boolean;
|
|
42
50
|
}
|
|
@@ -44,13 +52,13 @@ export interface SelectGroup {
|
|
|
44
52
|
* Navigation only — the parent row opens the submenu and is never itself selectable.
|
|
45
53
|
* While a filter query is active the tree flattens and a submenu collapses into a
|
|
46
54
|
* group (its `label` becomes the heading). `disabled` cascades to descendants. */
|
|
47
|
-
export interface SelectSubmenu {
|
|
55
|
+
export interface SelectSubmenu<V extends OptionValue = string> {
|
|
48
56
|
/** The parent row's label — and the group heading when filtering flattens it. */
|
|
49
57
|
label: string;
|
|
50
58
|
/** Leading icon on the parent row. */
|
|
51
59
|
icon?: IconShape;
|
|
52
60
|
/** The submenu's items — options, or further groups / submenus. */
|
|
53
|
-
submenu: SelectItem[];
|
|
61
|
+
submenu: SelectItem<V>[];
|
|
54
62
|
/** Disable the whole branch (cascades to all descendants). */
|
|
55
63
|
disabled?: boolean;
|
|
56
64
|
}
|
|
@@ -58,13 +66,13 @@ export interface SelectSubmenu {
|
|
|
58
66
|
* inline `SelectGroup`, or a flyout `SelectSubmenu`. Discriminated by shape — an
|
|
59
67
|
* `options` array is a group, a `submenu` array is a submenu, else it's an option.
|
|
60
68
|
* (`options` / `submenu` are therefore reserved keys on an option object.) */
|
|
61
|
-
export type SelectItem = string | SelectOption | SelectGroup | SelectSubmenu
|
|
69
|
+
export type SelectItem<V extends OptionValue = string> = string | SelectOption<V> | SelectGroup<V> | SelectSubmenu<V>;
|
|
62
70
|
/** Per-row snapshot passed to `renderOption` / `renderIndicator`. Everything here
|
|
63
71
|
* is known at render time; the combobox "active" cursor is deliberately absent
|
|
64
72
|
* (it's a live element state the DOM owns, not a render-time value). */
|
|
65
|
-
export interface OptionState {
|
|
73
|
+
export interface OptionState<V extends OptionValue = string> {
|
|
66
74
|
/** The option's value. */
|
|
67
|
-
value:
|
|
75
|
+
value: V;
|
|
68
76
|
/** Whether this row is currently selected. */
|
|
69
77
|
selected: boolean;
|
|
70
78
|
/** Whether this row is disabled. */
|
|
@@ -81,15 +89,15 @@ export interface EmptyState {
|
|
|
81
89
|
}
|
|
82
90
|
/** Snapshot passed to `renderTrigger` so a custom trigger can reflect the current
|
|
83
91
|
* selection and open state. */
|
|
84
|
-
export interface TriggerState {
|
|
92
|
+
export interface TriggerState<V extends OptionValue = string> {
|
|
85
93
|
/** Whether the menu is open — use it for `aria-expanded` and a chevron. */
|
|
86
94
|
open: boolean;
|
|
87
|
-
/** The current selection: a
|
|
95
|
+
/** The current selection: a single value (single), an array (`multiple`), or
|
|
88
96
|
* `undefined` when nothing is chosen. */
|
|
89
|
-
value:
|
|
97
|
+
value: V | V[] | undefined;
|
|
90
98
|
/** The resolved option objects for the current selection (empty when none).
|
|
91
99
|
* `selected.length` is the multi-select count. */
|
|
92
|
-
selected: SelectOption[];
|
|
100
|
+
selected: SelectOption<V>[];
|
|
93
101
|
/** Whether the whole select is disabled. */
|
|
94
102
|
disabled: boolean;
|
|
95
103
|
/** The `icon` shape passed to `Select`, if any — hand it to your trigger
|
|
@@ -102,12 +110,12 @@ export interface TriggerState {
|
|
|
102
110
|
* row, or the `clearable` "Clear" footer) carries `all: true` instead. **Always
|
|
103
111
|
* narrow on `'all' in attrs` before reading `option`** — the `all` variant fires
|
|
104
112
|
* for single-select too (via `clearable`), not just multiple. */
|
|
105
|
-
export type SelectChangeAttrs = {
|
|
113
|
+
export type SelectChangeAttrs<V extends OptionValue = string> = {
|
|
106
114
|
/** The option value that changed — the chosen value (single) or the toggled
|
|
107
115
|
* row (multiple). */
|
|
108
|
-
value:
|
|
116
|
+
value: V;
|
|
109
117
|
/** The resolved option object for `value`. */
|
|
110
|
-
option: SelectOption
|
|
118
|
+
option: SelectOption<V>;
|
|
111
119
|
/** Multiple only: whether the change turned selection **on** (true) or off. */
|
|
112
120
|
selected?: boolean;
|
|
113
121
|
} | {
|
|
@@ -121,17 +129,21 @@ export type SelectChangeAttrs = {
|
|
|
121
129
|
/** Props shared by both selection modes, intersected into `SelectProps`. Exported
|
|
122
130
|
* (and kept as an interface intersected — not a union base via `extends`) so its
|
|
123
131
|
* members read as `Select`'s *own* props in the generated docs, not inherited. */
|
|
124
|
-
export interface SelectCommonProps extends Omit<BaseProps, 'children'> {
|
|
132
|
+
export interface SelectCommonProps<V extends OptionValue = string> extends Omit<BaseProps, 'children'> {
|
|
125
133
|
/** The options to choose from — bare strings, `SelectOption` objects, `SelectGroup`s
|
|
126
134
|
* (inline titled sections), or `SelectSubmenu`s (flyout branches). Groups and
|
|
127
135
|
* submenus nest and mix with plain options. Selection stays global (one `value`,
|
|
128
136
|
* leaf options only); a filter query flattens the tree into grouped results.
|
|
129
137
|
*
|
|
138
|
+
* `Select` infers its value type `V` from these options: `{ value: 365 }` makes
|
|
139
|
+
* `onValueChange` report `number`. A mix of value types widens `V` to the union.
|
|
140
|
+
*
|
|
130
141
|
* Each leaf `value` is the option's identity and must be **unique across the whole
|
|
131
|
-
* tree**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
|
|
142
|
+
* tree** (selection is value-keyed, so a value repeated in two sections is one logical
|
|
143
|
+
* pick: both rows toggle together, the trigger resolves to the last). Values that
|
|
144
|
+
* stringify alike (`365` and `"365"`) also collide as row keys; dev builds
|
|
145
|
+
* `console.warn` on either. */
|
|
146
|
+
options: SelectItem<V>[];
|
|
135
147
|
/** Preferred placement of the options menu relative to the trigger. The menu
|
|
136
148
|
* auto-flips vertically and clamps horizontally when needed.
|
|
137
149
|
* @defaultValue bottom-start */
|
|
@@ -183,7 +195,7 @@ export interface SelectCommonProps extends Omit<BaseProps, 'children'> {
|
|
|
183
195
|
* type. `true` uses the built-in matcher — a case-insensitive substring of the
|
|
184
196
|
* option's **value / label / hint**. Pass a **function** `(option, query) =>
|
|
185
197
|
* boolean` for custom matching (called per option; return `true` to keep it). */
|
|
186
|
-
filter?: boolean | ((option: SelectOption
|
|
198
|
+
filter?: boolean | ((option: SelectOption<V>, query: string) => boolean);
|
|
187
199
|
/** `multiple` only: a "Select all" row at the top that toggles every enabled
|
|
188
200
|
* option (the currently-visible ones when a `filter` query is active); its box
|
|
189
201
|
* shows the mixed state when only some are selected. On by default in
|
|
@@ -208,13 +220,13 @@ export interface SelectCommonProps extends Omit<BaseProps, 'children'> {
|
|
|
208
220
|
* `OptionState` (`value`/`selected`/`disabled`). Filtering still works (it
|
|
209
221
|
* matches the option's `value`/`label`/`hint`), but match-highlighting is
|
|
210
222
|
* skipped — your content owns its own display. */
|
|
211
|
-
renderOption?: (option: SelectOption
|
|
223
|
+
renderOption?: (option: SelectOption<V>, state: OptionState<V>) => React.ReactNode;
|
|
212
224
|
/** Replace each row's selection **mark** with your own node, drawn at the
|
|
213
225
|
* leading edge. The row stays the control (`role` + `aria-checked` from
|
|
214
226
|
* `indicator` / `selection`); only the drawn mark changes, so pair it with an
|
|
215
227
|
* `indicator` (`'check'` / `'radio'`) or `selection="multiple"` for the
|
|
216
228
|
* semantics. Composes with `renderOption`. */
|
|
217
|
-
renderIndicator?: (state: OptionState) => React.ReactNode;
|
|
229
|
+
renderIndicator?: (state: OptionState<V>) => React.ReactNode;
|
|
218
230
|
/** Render your own trigger in place of the default field. Receives a
|
|
219
231
|
* `TriggerState` (`open` / `value` / `selected` / `disabled` / `icon`) to drive
|
|
220
232
|
* its look. **Return exactly one focusable element** (an Anta `Button`, say) —
|
|
@@ -225,7 +237,7 @@ export interface SelectCommonProps extends Omit<BaseProps, 'children'> {
|
|
|
225
237
|
* trigger owns its own styling: the field props (`label`, `hint`, `size`, `status`,
|
|
226
238
|
* `placeholder`, `round`) and `className` / `style` apply to the *default* trigger
|
|
227
239
|
* only — put your own on the element you return. */
|
|
228
|
-
renderTrigger?: (state: TriggerState) => React.ReactNode;
|
|
240
|
+
renderTrigger?: (state: TriggerState<V>) => React.ReactNode;
|
|
229
241
|
/** Render content in the menu body when the (filtered) option list is empty —
|
|
230
242
|
* a "no results" message, a loading indicator (gated on your own external
|
|
231
243
|
* loading state), or a "create from the query" row. Receives an `EmptyState`
|
|
@@ -239,37 +251,37 @@ export interface SelectCommonProps extends Omit<BaseProps, 'children'> {
|
|
|
239
251
|
* `<Select>` props. `selection` discriminates the value shape: single mode
|
|
240
252
|
* (`value: string`) or `multiple` (`value: string[]`).
|
|
241
253
|
*/
|
|
242
|
-
export type SelectProps = SelectCommonProps & ({
|
|
243
|
-
/** Selection mode. `'single'` (the default) keeps `value` a
|
|
254
|
+
export type SelectProps<V extends OptionValue = string> = SelectCommonProps<V> & ({
|
|
255
|
+
/** Selection mode. `'single'` (the default) keeps `value` a single value and
|
|
244
256
|
* closes the menu on pick. Switch to `'multiple'` for checkboxes + an
|
|
245
257
|
* array value.
|
|
246
258
|
* @defaultValue single */
|
|
247
259
|
selection?: 'single';
|
|
248
|
-
/** Controlled value — the `value`
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
value?:
|
|
253
|
-
/** Initial value (an option's `value`
|
|
260
|
+
/** Controlled value — the `value` of the selected option. When provided, the
|
|
261
|
+
* consumer owns selection: the field follows this prop and a pick only
|
|
262
|
+
* *requests* a change via `onValueChange` (reject by not updating). Leave
|
|
263
|
+
* undefined for uncontrolled. */
|
|
264
|
+
value?: V;
|
|
265
|
+
/** Initial value (an option's `value`) for the uncontrolled case — the
|
|
254
266
|
* wrapper then owns it. */
|
|
255
|
-
defaultValue?:
|
|
267
|
+
defaultValue?: V;
|
|
256
268
|
/** Fires after the selection changes, with the new value and a
|
|
257
269
|
* `{ value, option }` snapshot. Select has no discrete element state, so
|
|
258
270
|
* there is no cancelable `onStateChange` (see the Input event-model note). */
|
|
259
|
-
onValueChange?: (value:
|
|
271
|
+
onValueChange?: (value: V, attrs: SelectChangeAttrs<V>) => void;
|
|
260
272
|
} | {
|
|
261
273
|
/** Multi-select: checkboxes on every row, the menu stays open while
|
|
262
274
|
* toggling, the field shows an "N selected" count, and `value` is an array. */
|
|
263
275
|
selection: 'multiple';
|
|
264
|
-
/** Controlled values — the `value`
|
|
276
|
+
/** Controlled values — the `value`s of the selected options (see the
|
|
265
277
|
* single-select `value` note). */
|
|
266
|
-
value?:
|
|
267
|
-
/** Initial values (option `value`
|
|
268
|
-
defaultValue?:
|
|
278
|
+
value?: V[];
|
|
279
|
+
/** Initial values (option `value`s) for the uncontrolled case. */
|
|
280
|
+
defaultValue?: V[];
|
|
269
281
|
/** Fires after any toggle, with the new value array and a `{ value, option,
|
|
270
282
|
* selected }` snapshot of the row that changed (or `{ all: true }` for the
|
|
271
283
|
* Select-all row). */
|
|
272
|
-
onValueChange?: (value:
|
|
284
|
+
onValueChange?: (value: V[], attrs: SelectChangeAttrs<V>) => void;
|
|
273
285
|
});
|
|
274
286
|
/** Rolled-up selection of a group / submenu subtree: `'none'` / `'all'` of the
|
|
275
287
|
* descendant leaves selected, or `'some'` in between. On `SelectedGroup` /
|
|
@@ -277,26 +289,26 @@ export type SelectProps = SelectCommonProps & ({
|
|
|
277
289
|
export type SelectionState = 'none' | 'some' | 'all';
|
|
278
290
|
/** A leaf option annotated with its current selection — a `SelectOption` plus
|
|
279
291
|
* `selected`. Returned by {@link optionsWithSelection}. */
|
|
280
|
-
export type SelectedOption = SelectOption & {
|
|
292
|
+
export type SelectedOption<V extends OptionValue = string> = SelectOption<V> & {
|
|
281
293
|
selected: boolean;
|
|
282
294
|
};
|
|
283
295
|
/** A group with its descendants annotated and a rolled-up `selectionState`.
|
|
284
296
|
* Returned by {@link optionsWithSelection}. */
|
|
285
|
-
export interface SelectedGroup extends Omit<SelectGroup
|
|
286
|
-
options: SelectedItem[];
|
|
297
|
+
export interface SelectedGroup<V extends OptionValue = string> extends Omit<SelectGroup<V>, 'options'> {
|
|
298
|
+
options: SelectedItem<V>[];
|
|
287
299
|
selectionState: SelectionState;
|
|
288
300
|
}
|
|
289
301
|
/** A submenu with its descendants annotated and a rolled-up `selectionState`.
|
|
290
302
|
* Returned by {@link optionsWithSelection}. */
|
|
291
|
-
export interface SelectedSubmenu extends Omit<SelectSubmenu
|
|
292
|
-
submenu: SelectedItem[];
|
|
303
|
+
export interface SelectedSubmenu<V extends OptionValue = string> extends Omit<SelectSubmenu<V>, 'submenu'> {
|
|
304
|
+
submenu: SelectedItem<V>[];
|
|
293
305
|
selectionState: SelectionState;
|
|
294
306
|
}
|
|
295
307
|
/** One node of the tree from {@link optionsWithSelection}: a leaf `SelectedOption`
|
|
296
308
|
* (with `selected`), or a `SelectedGroup` / `SelectedSubmenu` (annotated children
|
|
297
309
|
* + rolled-up `selectionState`). Mirrors `SelectItem` minus the bare-string
|
|
298
310
|
* shorthand — strings are normalized to `SelectedOption`. */
|
|
299
|
-
export type SelectedItem = SelectedOption | SelectedGroup | SelectedSubmenu
|
|
311
|
+
export type SelectedItem<V extends OptionValue = string> = SelectedOption<V> | SelectedGroup<V> | SelectedSubmenu<V>;
|
|
300
312
|
/**
|
|
301
313
|
* Project a `Select` `options` tree onto a set of selected values: returns a mirror
|
|
302
314
|
* of the tree with every leaf marked `selected` and every group / submenu carrying a
|
|
@@ -319,7 +331,7 @@ export type SelectedItem = SelectedOption | SelectedGroup | SelectedSubmenu;
|
|
|
319
331
|
* // { value: 'eng-fe', label: 'Frontend', selected: false }, … ] }
|
|
320
332
|
* ```
|
|
321
333
|
*/
|
|
322
|
-
export declare function optionsWithSelection(options: SelectItem[], values:
|
|
334
|
+
export declare function optionsWithSelection<V extends OptionValue = string>(options: SelectItem<V>[], values: V | V[] | undefined): SelectedItem<V>[];
|
|
323
335
|
/**
|
|
324
336
|
* `<Select>` — a single- or multi-select dropdown, composed from `<Input>` (a
|
|
325
337
|
* read-only trigger) and `<Menu>` (the options). `selection` sets behaviour:
|
|
@@ -345,4 +357,4 @@ export declare function optionsWithSelection(options: SelectItem[], values: stri
|
|
|
345
357
|
* />
|
|
346
358
|
* ```
|
|
347
359
|
*/
|
|
348
|
-
export declare const Select: (props: SelectProps) => any;
|
|
360
|
+
export declare const Select: <V extends OptionValue = string>(props: SelectProps<V>) => any;
|