@antadesign/anta 0.3.11 → 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.
Files changed (73) hide show
  1. package/README.md +31 -36
  2. package/dist/anta_helpers.js +5 -0
  3. package/dist/components/Button.d.ts +20 -64
  4. package/dist/components/Button.js +15 -27
  5. package/dist/components/ButtonCopy.d.ts +43 -25
  6. package/dist/components/ButtonCopy.js +46 -4
  7. package/dist/components/Expander.js +1 -1
  8. package/dist/components/InputDate.d.ts +9 -1
  9. package/dist/components/InputDate.js +4 -1
  10. package/dist/components/Menu.d.ts +6 -5
  11. package/dist/components/MenuItem.d.ts +2 -9
  12. package/dist/components/MenuItem.js +2 -16
  13. package/dist/components/MenuItemCopy.d.ts +22 -19
  14. package/dist/components/MenuItemCopy.js +38 -3
  15. package/dist/components/Select.d.ts +63 -51
  16. package/dist/components/Select.js +9 -8
  17. package/dist/components/SelectFaceted.d.ts +21 -16
  18. package/dist/components/SelectFaceted.js +2 -2
  19. package/dist/components/Text.d.ts +2 -2
  20. package/dist/components/Title.d.ts +2 -2
  21. package/dist/components/Tooltip.d.ts +1 -1
  22. package/dist/components/copy-props.d.ts +72 -0
  23. package/dist/components/copy-props.js +20 -0
  24. package/dist/components/select-options.d.ts +10 -5
  25. package/dist/components/select-options.js +1 -1
  26. package/dist/elements/a-button.css +1 -1
  27. package/dist/elements/a-button.d.ts +0 -7
  28. package/dist/elements/a-button.js +0 -23
  29. package/dist/elements/a-checkbox.css +1 -1
  30. package/dist/elements/a-copy.css +1 -0
  31. package/dist/elements/a-copy.d.ts +16 -0
  32. package/dist/elements/a-copy.js +148 -0
  33. package/dist/elements/a-expander.css +1 -1
  34. package/dist/elements/a-expander.js +1 -18
  35. package/dist/elements/a-input-time.css +1 -1
  36. package/dist/elements/a-input.css +1 -1
  37. package/dist/elements/a-input.js +2 -2
  38. package/dist/elements/a-menu-item.css +1 -1
  39. package/dist/elements/a-menu-item.d.ts +6 -6
  40. package/dist/elements/a-menu-item.js +0 -4
  41. package/dist/elements/a-menu.d.ts +6 -2
  42. package/dist/elements/a-menu.js +67 -31
  43. package/dist/elements/a-radio-group.css +1 -1
  44. package/dist/elements/a-radio-group.d.ts +1 -0
  45. package/dist/elements/a-radio-group.js +11 -2
  46. package/dist/elements/a-radio.css +1 -1
  47. package/dist/elements/a-tabs.css +1 -1
  48. package/dist/elements/a-tabs.d.ts +1 -0
  49. package/dist/elements/a-tabs.js +35 -6
  50. package/dist/elements/a-tag.css +1 -1
  51. package/dist/elements/a-text.css +1 -1
  52. package/dist/elements/a-title.css +1 -1
  53. package/dist/elements/a-tooltip.js +1 -1
  54. package/dist/elements/copy-behavior.d.ts +6 -5
  55. package/dist/elements/index.d.ts +1 -0
  56. package/dist/elements/index.js +3 -0
  57. package/dist/elements/key-activation.d.ts +5 -9
  58. package/dist/elements/key-activation.js +0 -1
  59. package/dist/general_types.d.ts +32 -41
  60. package/dist/index.d.ts +3 -2
  61. package/dist/jsx-runtime.d.ts +2 -1
  62. package/dist/reset.css +1 -1
  63. package/dist/theme-anta.css +1 -0
  64. package/dist/tokens.css +1 -1
  65. package/package.json +2 -1
  66. package/dist/components/Card.module.css +0 -35
  67. package/dist/components/Progress.module.css +0 -0
  68. package/dist/components/Radio.d.ts +0 -37
  69. package/dist/components/Radio.js +0 -33
  70. package/dist/components/Tab.d.ts +0 -37
  71. package/dist/components/Tab.js +0 -5
  72. package/dist/components/Tabs.module.css +0 -1
  73. 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 with plain HTML, and JSX wrappers for React and Preact.
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
- The Antithesis SaaS has an unusual architecture: most of its UI code runs inside a Worker thread, driven by a custom reactive engine that powers Antithesis notebooks. To keep state in sync between the Worker and the UI thread, Anta's web components must be **fully declarative** — they never mutate their own attributes (any internal state changes happen inside Shadow DOM, invisible to the outer document). A self-mutating attribute would break the Worker↔UI sync.
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
- `@antadesign/anta` is an NPM package, so you can `npm install @antadesign/anta` or do that with `pnpm` / `bun`.
9
+ ```sh
10
+ npm install @antadesign/anta # or pnpm / bun
11
+ ```
12
12
 
13
- Use the latest version from npm, but **always pin an exact version** — `"@antadesign/anta": "0.2.0"` in your `package.json` — rather than a floating tag like `"latest"` or `"dev"`, which can change between installs.
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
- Anta exposes four independent imports. Tokens + elements + the JSX layer are the minimum to render a styled component; the reset is recommended but skippable.
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 — `--bg-1…5`, `--text-1…5`, `--border-1…5`, the `.dark`-ancestor toggling, the base `font-size: 15px`. Also declares the `@layer base, anta, components, utilities;` cascade order. | You're applying your own design tokens at the same variable names. |
33
- | `@antadesign/anta/reset.css` | Modern small reset (box-sizing, margin reset, replaced-element block, form-control font inheritance, text-wrap defaults) plus Anta's typography opinions for `h1-h6`, `strong`, `ul / ol / menu`, `a` / link states. Lives in `@layer anta`. | You already have a reset and don't want Anta's typography defaults. |
34
- | `@antadesign/anta/elements` | Side-effect import that registers `<a-progress>`, `<a-text>`, `<a-icon>` as custom elements *and* attaches their per-element CSS (also in `@layer anta`). | You're rendering Anta only on the server (no DOM) and never hydrating. |
35
- | `@antadesign/anta` | The JSX wrappers (`Progress`, `Text`, `Icon`) — typed React/Preact components that emit `<a-*>` tags. | You're writing the `<a-*>` elements by hand and don't need a JSX layer. |
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 chain matters: the per-element CSS that ships with `/elements` references variables like `var(--text-1)` and `var(--bg-2)`. Those variables are *only defined* by `tokens.css`. Skip the tokens import and the components render with whatever the surrounding cascade provides — usually nothing styled at all.
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 an order of `@layer base, anta, components, utilities;`, which keeps Anta's defaults above any preflight resets (Tailwind's `@layer base`, Normalize, etc.) while letting your own `@layer components` rules and utility-class frameworks like Tailwind (`@layer utilities`) override Anta when you ask them to.
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
- If you need a different order, declare it in your *own* CSS file that loads **before** `tokens.css`. The first mention of each layer name fixes its position:
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 resets defeat Anta's element rules.**
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 **always** beat layered ones in the cascade, regardless of specificity. So `* { margin: 0 }` outranks Anta's `caption { margin-bottom: 0.25em }`, `p { margin: 0 0 1em }`, `ul / ol` padding, and any other per-element default Anta provides — even though those use stronger selectors.
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 (React components) as `Progress` render custom DOM elements as `<a-progress>`. The custom elements themselves must be registered with the browser **before** they appear in the DOM, and registration only works where `HTMLElement` exists — i.e. the UI thread of a real browser. **Node.js (SSR) and Worker threads don't have `HTMLElement`**, so the import is harmless in those environments: it does nothing — registration is skipped silently and the class uses a stand-in base instead of crashing — though it might extend your worker's bundle size a bit.
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` is the all-in-one convenience import. To trim your bundle, import the specific element entry points instead — each registers just that element and loads just its CSS:
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 styles are side-effect imports (the act of importing registers the element), and both are idempotent and SSR-safe. The granular form keeps unused elements — and any dependencies they pull in — out of your bundle.
77
+ Both forms are side-effect imports, idempotent, and SSR-safe.
83
78
 
84
- The cleanest pattern is a **static, synchronous import at your app's entry file** — outside any component, outside any hook:
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 by the time any component renders an `<a-progress>`, the custom element class is already registered — there's no flash of un-upgraded elements.
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 to put the static import depends on the runtime:
94
+ Where the static import goes depends on the runtime:
101
95
 
102
- **Plain HTML / static sites** — put it in a `<script type="module">` tag in the document head. That's client-side by default.
96
+ **Plain HTML / static sites** — a `<script type="module">` tag in the document head.
103
97
 
104
- **SSR frameworks (Astro, Next.js)** — register from a script that the framework only ships 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 Client Component file (the one with `'use client'` at the top) — that file is only bundled into the client chunk, so the import never reaches Node.
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
- **React / Preact apps where the UI runs in a Worker thread (the Antithesis setup)** — register the elements in your UI-thread bootstrap code, the script that owns the real DOM. The Worker won't have `HTMLElement`, so the import must not run there.
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, an older `@types/react` without the `React.JSX` namespace), merge anta's tag map into your JSX namespace yourself. Anta exports it as a standalone interface, `AntaIntrinsicElements` — and `@antadesign/stickers` exports the matching `StickerIntrinsicElements` — so it's a single `extends` in any `.d.ts` covered by your tsconfig:
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 with a customized version of <a href="https://typetype.org/fonts/tt-interphases-pro" target="_blank" rel="noopener noreferrer">TT Interphases Pro</a> in mind, but it doesn't ship any font binaries. Components reference families through the `--sans-serif` and `--monospace` CSS variables and fall back to native system stacks when no font is registered. The base size is `font-size: 15px` on `:root` (so `1rem = 15px`), intentionally diverging from the browser default of 16px to match Antithesis's information-dense layouts — both the variables and the base size live in `tokens.css`.
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
 
@@ -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) {
@@ -55,13 +55,24 @@ export type ContentMode = {
55
55
  iconTrailing?: IconShape;
56
56
  };
57
57
  /** Submit axis — anchors (href) don't carry form-submission props; buttons
58
- * don't carry anchor props. A link (`href`) also can't be a copy control, so the
59
- * copy props live only on the non-link branch (via `CopyMode`) and are `never`
60
- * on the link branch — a `<ButtonCopy href=…>` or `<Button href=… copy=…>` is a
61
- * type error, not a silently-ignored attribute. */
58
+ * don't carry anchor props. (Copy is no longer a Button concern — `ButtonCopy`
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`). */
62
71
  export type SubmitMode = {
63
- /** Renders as `<a role="button">` instead of `<a-button>`. */
64
- href: string;
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;
65
76
  /** Anchor target. */
66
77
  target?: string;
67
78
  /** Anchor rel. */
@@ -72,13 +83,7 @@ export type SubmitMode = {
72
83
  ping?: string;
73
84
  type?: never;
74
85
  form?: never;
75
- copy?: never;
76
- copyNode?: never;
77
- copyUrl?: never;
78
- copyWithUrl?: never;
79
- onCopyRequest?: never;
80
- onCopied?: never;
81
- } | ({
86
+ } | {
82
87
  href?: never;
83
88
  target?: never;
84
89
  rel?: never;
@@ -88,7 +93,7 @@ export type SubmitMode = {
88
93
  type?: 'button' | 'submit' | 'reset';
89
94
  /** Form id when the button isn't a descendant of its form. */
90
95
  form?: string;
91
- } & CopyMode);
96
+ };
92
97
  /** Priority axis — `underline` only on `tertiary` / `quaternary`,
93
98
  * `paddingless` only on `quaternary`. */
94
99
  export type PriorityMode = {
@@ -109,55 +114,6 @@ export type PriorityMode = {
109
114
  /** Drops outer padding to zero. */
110
115
  paddingless?: boolean;
111
116
  };
112
- /** Copy axis — turns the button into a copy control that writes to the clipboard
113
- * on click and flashes a success / failure state (see `<a-button>`'s copy
114
- * behavior). **Exactly one** of `copy`, `copyNode`, or `copyUrl` may be set;
115
- * the union makes the others `never` in each mode. For the batteries-included
116
- * preset, use `ButtonCopy`. */
117
- export type CopyMode = {
118
- copy?: never;
119
- copyNode?: never;
120
- copyUrl?: never;
121
- copyWithUrl?: never;
122
- onCopied?: never;
123
- onCopyRequest?: never;
124
- } | {
125
- /** Text copied to the clipboard on click. */
126
- copy: string;
127
- /** Prefix the copied text with `// URL: <current page URL>`. */
128
- copyWithUrl?: boolean;
129
- /** Compute the copy content lazily. Fires on **pointerdown**; update `copy`
130
- * (a state change) here and the click copies the latest value. The
131
- * pointerdown→click gap lets the update land even when this handler runs
132
- * off the UI thread (a worker-rendered app) — only the serializable `copy`
133
- * string crosses, via the normal re-render. */
134
- onCopyRequest?: () => void;
135
- copyNode?: never;
136
- copyUrl?: never;
137
- /** Fires after the copy attempt with whether it succeeded. */
138
- onCopied?: (ok: boolean) => void;
139
- } | {
140
- /** Copy a DOM node as rich text instead of a string. `true` copies the
141
- * nearest ancestor marked `data-copy-source`; a string is a CSS selector
142
- * for an ancestor region (`closest`). The copy control is stripped from
143
- * the copied output. */
144
- copyNode: boolean | string;
145
- copy?: never;
146
- copyUrl?: never;
147
- copyWithUrl?: never;
148
- onCopyRequest?: never;
149
- /** Fires after the copy attempt with whether it succeeded. */
150
- onCopied?: (ok: boolean) => void;
151
- } | {
152
- /** Copy the current page URL (`location.href`). */
153
- copyUrl: true;
154
- copy?: never;
155
- copyNode?: never;
156
- copyWithUrl?: never;
157
- onCopyRequest?: never;
158
- /** Fires after the copy attempt with whether it succeeded. */
159
- onCopied?: (ok: boolean) => void;
160
- };
161
117
  export type ButtonProps = BaseButtonProps & PriorityMode & ContentMode & SubmitMode & BaseProps;
162
118
  /**
163
119
  * Action button.
@@ -179,4 +135,4 @@ export type ButtonProps = BaseButtonProps & PriorityMode & ContentMode & SubmitM
179
135
  * <Button href="/docs" target="_blank" label="Read the docs" />
180
136
  * ```
181
137
  */
182
- export declare const Button: ({ priority, tone, underline, icon, iconTrailing, paddingless, label, size, loading, disabled, selected, round, href, type, form, copy, copyNode, copyUrl, copyWithUrl, onCopied, onCopyRequest, 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;
@@ -1,5 +1,5 @@
1
1
  import { Fragment, jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
- import { toneStyle, roundStyle, roundAttr, wrapLabel, nativeStateChange } from "../anta_helpers";
2
+ import { toneStyle, roundStyle, roundAttr, wrapLabel } from "../anta_helpers";
3
3
  const Button = ({
4
4
  priority,
5
5
  tone,
@@ -14,24 +14,20 @@ 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
- copy,
20
- copyNode,
21
- copyUrl,
22
- copyWithUrl,
23
- onCopied,
24
- onCopyRequest,
25
23
  className,
26
24
  style,
27
25
  children,
28
26
  ...rest
29
27
  }) => {
30
- const isCopy = copy != null || copyNode != null || copyUrl === true;
31
28
  const toneAttr = tone || void 0;
32
29
  const computedStyle = roundStyle(round, "--button-round", toneStyle(toneAttr, "--button-tone-source", style));
33
- const leadingIcon = icon ?? (isCopy ? "copy" : void 0);
34
- const isIconOnly = leadingIcon != null && label == null && children == null && iconTrailing == null;
30
+ const isIconOnly = icon != null && label == null && children == null && iconTrailing == null;
35
31
  const sharedAttrs = {
36
32
  // `<a-button>` is a custom element with no implicit ARIA role, so AT would
37
33
  // announce it as a generic clickable — and `aria-pressed` below is only
@@ -61,27 +57,15 @@ const Button = ({
61
57
  "aria-disabled": disabled || loading ? "true" : void 0,
62
58
  "aria-busy": loading ? "true" : void 0,
63
59
  "aria-pressed": selected ? "true" : void 0,
64
- // Icon-only buttons get an accessible name: "Copy" for a copy control (its
65
- // glyph carries no text), else the icon shape. Consumer's own `aria-label`
66
- // (via ...rest) wins by spread order.
67
- "aria-label": isIconOnly ? isCopy ? "Copy" : icon : void 0,
68
- // Copy behavior — the element reads these and performs the write itself.
69
- copy: copy != null ? copy : void 0,
70
- "copy-node": copyNode === true ? "" : typeof copyNode === "string" ? copyNode : void 0,
71
- "copy-url": copyUrl ? "" : void 0,
72
- "copy-with-url": copyWithUrl ? "" : void 0,
73
- // Marks the control so `copy-node` serialization strips it from the copied
74
- // node (a copy button inside the copied region shouldn't paste itself).
75
- "data-copy-node-button": copyNode != null && copyNode !== false ? "" : void 0,
76
- oncopydone: onCopied ? (e) => onCopied(nativeStateChange(e).detail?.ok ?? false) : void 0,
77
- // Fired on pointerdown for a lazy copy — the consumer updates `copy` here
78
- // (a state change) and the click copies the latest value. No payload crosses.
79
- oncopyrequest: onCopyRequest ? () => onCopyRequest() : void 0,
60
+ // Icon-only buttons get an accessible name from the icon shape. Consumer's
61
+ // own `aria-label` (via ...rest) wins by spread order — that's the channel
62
+ // ButtonCopy uses to name an icon-only copy button "Copy".
63
+ "aria-label": isIconOnly ? icon : void 0,
80
64
  class: className,
81
65
  style: computedStyle
82
66
  };
83
67
  const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
84
- leadingIcon && /* @__PURE__ */ jsx("a-icon", { shape: leadingIcon, "aria-hidden": "true" }),
68
+ icon && /* @__PURE__ */ jsx("a-icon", { shape: icon, "aria-hidden": "true" }),
85
69
  label != null && /* @__PURE__ */ jsx("a-button-label", { children: label }),
86
70
  wrapLabel(children, "a-button-label"),
87
71
  iconTrailing && /* @__PURE__ */ jsx("a-icon", { shape: iconTrailing, "aria-hidden": "true" })
@@ -92,6 +76,10 @@ const Button = ({
92
76
  {
93
77
  href,
94
78
  "data-anta": "",
79
+ target,
80
+ rel,
81
+ download,
82
+ ping,
95
83
  ...sharedAttrs,
96
84
  ...rest,
97
85
  children: inner
@@ -1,38 +1,56 @@
1
1
  import { type ButtonProps } from './Button';
2
- /** `ButtonProps` narrowed so a copy target is required — a copy button must copy
3
- * something. Intersecting with the target union drops the "no copy prop" member
4
- * of `CopyMode` (its `copy?: never` makes each combination `never`). */
5
- export type ButtonCopyProps = ButtonProps & ({
6
- copy: string;
7
- } | {
8
- copyNode: boolean | string;
9
- } | {
10
- copyUrl: true;
11
- });
2
+ import { type CopyTarget } from './copy-props';
3
+ /** A copy button is a plain action control, so `ButtonCopy` keeps the `Button`
4
+ * axes that fit — tone / size / priority styling, `disabled` / `loading`, a
5
+ * leading `icon` + `label` — and pins the ones that don't to `never`: `selected`
6
+ * (it isn't a toggle), the whole submit/link axis (`href` / `target` / `rel` /
7
+ * `download` / `ping` / `type` / `form` — a copy button neither navigates nor
8
+ * submits), and the public `iconTrailing` (the glyph's side is `iconPlacement`'s
9
+ * job). Plus a required copy target and the placement knob. (Pinning to `never`
10
+ * keeps the intersection expanding fully in the docs props table, which `Omit`
11
+ * wouldn't.) */
12
+ export type ButtonCopyProps = ButtonProps & {
13
+ selected?: never;
14
+ href?: never;
15
+ target?: never;
16
+ rel?: never;
17
+ download?: never;
18
+ ping?: never;
19
+ type?: never;
20
+ form?: never;
21
+ iconTrailing?: never;
22
+ } & CopyTarget & {
23
+ /** Where the copy glyph sits relative to the label — or `'none'` to omit it,
24
+ * in which case the feedback is a ghost of the label floating up (`toast`)
25
+ * plus a brief success / failure tone flash.
26
+ * @defaultValue 'leading' */
27
+ iconPlacement?: 'leading' | 'trailing' | 'none';
28
+ };
12
29
  /**
13
30
  * Copy button — a `Button` preset for copy-to-clipboard. Set `copy` for a literal
14
31
  * string, `copyNode` to copy a DOM region, or `copyUrl` to copy the current page
15
32
  * URL. For content computed on demand, keep `copy` reactive and refresh it in
16
- * `onCopyRequest` (fired on pointerdown). The `<a-button>` element performs the
17
- * write and reports the result; this wrapper flips the leading icon to a check
18
- * (success) or ✕ (failure) and retones to `success` / `critical` for ~2s — a JSX
19
- * re-render, no element state (see `useCopyFeedback`).
33
+ * `onCopyRequest` (fired on pointerdown / keydown).
20
34
  *
21
- * With a `label` it's a labeled button; without one it's an icon-only copy button.
22
- * Everything else is a normal `Button` prop — `tone`, `priority`, `size`, `icon`
23
- * (the resting glyph), `onCopied`.
35
+ * It composes a plain `<Button>` with a slotted `<a-copy>` child that performs
36
+ * the write — the button itself carries no copy behavior. This wrapper flips the
37
+ * copy glyph to a check (success) or ✕ (failure) and retones to `success` /
38
+ * `critical` for ~2s (a re-render, no element state — see `useCopyFeedback`).
39
+ * `iconPlacement` puts that glyph leading (default), trailing, or `'none'`; with
40
+ * `'none'` the `<a-copy>` floats a ghost of the label upward instead.
41
+ *
42
+ * With a `label` it's a labeled button; without one it's an icon-only copy button
43
+ * (named "Copy" for assistive tech). Everything else is a normal `Button` prop —
44
+ * `tone`, `priority`, `size`, `icon` (the resting glyph), `onCopied`.
24
45
  *
25
46
  * @example
26
47
  * ```tsx
27
48
  * <ButtonCopy copy="npm i @antadesign/anta" label="Copy install" />
28
- * <ButtonCopy copy="https://anta.design" /> // icon-only
49
+ * <ButtonCopy copy="https://anta.design" /> // icon-only
50
+ * <ButtonCopy copy={code} label="Copy" iconPlacement="trailing" />
51
+ * <ButtonCopy copy={code} label="Copy snippet" iconPlacement="none" /> // ghost feedback
29
52
  * <ButtonCopy copyNode=".snippet" label="Copy block" priority="tertiary" />
30
- * <ButtonCopy copyUrl label="Copy link" /> // copies location.href
31
- * <ButtonCopy copy={code} copyWithUrl label="Copy snippet" /> // code + source URL
32
- *
33
- * // Lazy: compute on pointerdown, copied on the click that follows.
34
- * const [report, setReport] = useState('')
35
- * <ButtonCopy copy={report} onCopyRequest={() => setReport(buildReport())} label="Copy report" />
53
+ * <ButtonCopy copyUrl label="Copy link" /> // copies location.href
36
54
  * ```
37
55
  */
38
- export declare const ButtonCopy: ({ icon, tone, onCopied, ...rest }: ButtonCopyProps) => any;
56
+ export declare const ButtonCopy: ({ icon, iconPlacement, tone, onCopied, onCopyRequest, copy, copyNode, copyUrl, copyWithUrl, label, children, "aria-label": ariaLabel, ...rest }: ButtonCopyProps) => any;
@@ -1,9 +1,51 @@
1
- import { jsx } from "@antadesign/anta/jsx-runtime";
1
+ import { jsx, jsxs } from "@antadesign/anta/jsx-runtime";
2
2
  import { Button } from "./Button";
3
3
  import { useCopyFeedback } from "../anta_helpers";
4
- const ButtonCopy = ({ icon, tone, onCopied, ...rest }) => {
5
- const { shownIcon, shownTone, handleCopied } = useCopyFeedback(icon, tone, onCopied);
6
- return /* @__PURE__ */ jsx(Button, { icon: shownIcon, tone: shownTone, onCopied: handleCopied, ...rest });
4
+ import { copyElementProps, isNodeCopy } from "./copy-props";
5
+ const ButtonCopy = ({
6
+ icon,
7
+ iconPlacement = "leading",
8
+ tone,
9
+ onCopied,
10
+ onCopyRequest,
11
+ copy,
12
+ copyNode,
13
+ copyUrl,
14
+ copyWithUrl,
15
+ label,
16
+ children,
17
+ "aria-label": ariaLabel,
18
+ ...rest
19
+ }) => {
20
+ const restingIcon = iconPlacement === "none" ? void 0 : icon ?? "copy";
21
+ const { shownIcon, shownTone, handleCopied } = useCopyFeedback(restingIcon, tone, onCopied);
22
+ const iconSlot = iconPlacement === "trailing" ? { iconTrailing: shownIcon } : iconPlacement === "none" ? {} : { icon: shownIcon };
23
+ const hasText = label != null || children != null;
24
+ const copyAttrs = copyElementProps({
25
+ copy,
26
+ copyNode,
27
+ copyUrl,
28
+ copyWithUrl,
29
+ onCopyRequest,
30
+ onCopied: handleCopied,
31
+ // Ghost feedback only makes sense when there's no glyph to swap.
32
+ toast: iconPlacement === "none"
33
+ });
34
+ return /* @__PURE__ */ jsxs(
35
+ Button,
36
+ {
37
+ ...iconSlot,
38
+ label,
39
+ tone: shownTone,
40
+ "aria-label": ariaLabel ?? (hasText ? void 0 : "Copy"),
41
+ "data-copy-node-button": isNodeCopy({ copyNode }) ? "" : void 0,
42
+ ...rest,
43
+ children: [
44
+ children,
45
+ /* @__PURE__ */ jsx("a-copy", { ...copyAttrs })
46
+ ]
47
+ }
48
+ );
7
49
  };
8
50
  export {
9
51
  ButtonCopy
@@ -44,7 +44,7 @@ const Expander = ({
44
44
  ...rest,
45
45
  children: [
46
46
  titleNode,
47
- actions != null && /* @__PURE__ */ jsx("span", { slot: "actions", style: { display: "contents" }, children: actions }),
47
+ actions != null && /* @__PURE__ */ jsx("span", { slot: "actions", children: actions }),
48
48
  /* @__PURE__ */ jsx("a-expander-details", { children })
49
49
  ]
50
50
  }
@@ -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. The cross-axis suffix
4
- * (`-start` / `-end`) aligns the left / right edges; no suffix (`bottom` /
5
- * `top`) centers the menu on the trigger. The menu auto-flips vertically and
6
- * clamps horizontally when there isn't room.
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;