@antadesign/anta 0.1.1-dev.1 → 0.1.1-dev.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.
@@ -1,16 +1,90 @@
1
+ /** Common props for JSX component wrappers. */
1
2
  export interface BaseProps {
3
+ /** CSS class name. Merged with any internal classes by the component. */
2
4
  className?: string;
5
+ /** Inline styles applied to the root element. */
3
6
  style?: React.CSSProperties;
7
+ /** Child elements. When provided, replaces the component's default label/content. */
4
8
  children?: React.ReactNode;
5
9
  }
10
+ /** Attributes for intrinsic custom elements (`<a-*>` tags) in JSX. */
6
11
  export interface BaseAttributes {
12
+ /** HTML `class` attribute (standard DOM). */
7
13
  class?: string;
14
+ /** React/Preact-style class name. Alias for `class`. */
8
15
  className?: string;
16
+ /** Inline styles applied to the element. */
9
17
  style?: React.CSSProperties;
10
18
  children?: React.ReactNode;
19
+ /** Tab order. Set to `0` to make the element keyboard-focusable. */
20
+ tabIndex?: number;
21
+ /** ARIA role override. */
22
+ role?: string;
23
+ /** Keydown handler — used for keyboard-driven interactions. */
24
+ onKeyDown?: (e: any) => void;
25
+ /** Click handler — used for mouse / tap activation. */
26
+ onClick?: (e: any) => void;
11
27
  }
28
+ /**
29
+ * Attributes for the `<a-progress>` custom element.
30
+ *
31
+ * These are the low-level web component attributes. For the JSX wrapper with
32
+ * typed props and computed labels, use `Progress` from `@antadesign/anta`.
33
+ */
12
34
  export interface AProgressAttributes extends BaseAttributes {
35
+ /** Current progress value. */
13
36
  value?: number | string;
37
+ /** Maximum value. Defaults to 100. */
14
38
  max?: number | string;
39
+ /** Color variant. `'neutral'` is the default gray; `'info'` is blue. */
15
40
  tone?: 'neutral' | 'info';
41
+ /** ARIA role — the JSX wrapper sets this to `'progressbar'`. */
42
+ role?: string;
43
+ /** ARIA value-now (current). */
44
+ 'aria-valuenow'?: number | string;
45
+ /** ARIA value-max. */
46
+ 'aria-valuemax'?: number | string;
47
+ /** ARIA value-min (defaults to 0). */
48
+ 'aria-valuemin'?: number | string;
49
+ /** ARIA accessible name. */
50
+ 'aria-label'?: string;
51
+ }
52
+ /**
53
+ * Attributes for the `<a-text>` custom element.
54
+ *
55
+ * Low-level web component attributes; for the JSX wrapper use `Text`
56
+ * from `@antadesign/anta`.
57
+ */
58
+ export interface ATextAttributes extends BaseAttributes {
59
+ /** Visual priority. Maps to text-1..text-5. */
60
+ priority?: 'primary' | 'secondary' | 'tertiary' | 'quaternary' | 'quinary';
61
+ /** Color tint. Applies the matching `--text-{N}-{tone}` palette. */
62
+ tone?: 'brand' | 'success' | 'critical' | 'warning' | 'info';
63
+ /** Render as inline-block instead of the default block. */
64
+ inline?: boolean | string;
65
+ /** Truncate to N lines with a trailing ellipsis. The attribute value
66
+ * carries the line count (e.g. `"1"`, `"3"`); the count is also
67
+ * available via the `--line-clamp` CSS custom property set inline. */
68
+ truncate?: boolean | string | number;
69
+ /** Marks the host as expandable when paired with `truncate`. Adds
70
+ * the fade-out mask; the JSX wrapper renders the chevron and owns
71
+ * the click/keyboard expansion logic. */
72
+ expandable?: boolean | string;
73
+ /** ARIA disclosure state, mirrors the JSX wrapper's `expanded` flag. */
74
+ 'aria-expanded'?: boolean | 'true' | 'false';
75
+ }
76
+ /**
77
+ * Attributes for the `<a-icon>` custom element. The `shape` attribute
78
+ * value is typed as `string` here so the element accepts any consumer's
79
+ * generated shapes. The JSX wrapper (`Icon`) narrows it to `IconShape`.
80
+ */
81
+ export interface AIconAttributes extends BaseAttributes {
82
+ /** Which icon to render. */
83
+ shape?: string;
84
+ /** ARIA role — the JSX wrapper sets `'img'` when a label is provided. */
85
+ role?: string;
86
+ /** ARIA accessible name when the icon carries meaning. */
87
+ 'aria-label'?: string;
88
+ /** Hides decorative icons from screen readers. */
89
+ 'aria-hidden'?: 'true' | 'false' | boolean;
16
90
  }
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * generate-icons.mjs — read a folder of SVGs, emit a CSS file with one
4
+ * `a-icon[shape="…"]` rule per icon and a `.d.ts` file that augments
5
+ * Anta's `IconShapes` interface so the new shapes flow into `IconShape`.
6
+ *
7
+ * Run from CLI:
8
+ * node scripts/generate-icons.mjs --input <svg-folder> --output <out-folder> --name <basename>
9
+ *
10
+ * Or import programmatically:
11
+ * import { generate } from '@antadesign/anta/generate-icons.mjs'
12
+ * await generate({ input, output, name, builtInShapes })
13
+ *
14
+ * Defaults:
15
+ * --name = "a-icon.shapes"
16
+ * --output = "."
17
+ * --input is required
18
+ */
19
+
20
+ import fs from 'node:fs'
21
+ import path from 'node:path'
22
+ import { fileURLToPath } from 'node:url'
23
+
24
+ // SVGs whose names match these get a "collision" warning so consumers
25
+ // know they're shadowing a built-in. Anta passes its own list when
26
+ // generating its own shapes; for consumers, we include the canonical
27
+ // built-in names so generating a same-named shape is flagged.
28
+ const DEFAULT_BUILT_IN = [
29
+ 'chevron-down', 'chevron-up', 'chevron-left', 'chevron-right',
30
+ 'check', 'x', 'info', 'warning-triangle', 'plus', 'minus', 'menu', 'copy',
31
+ ]
32
+
33
+ function encodeSvg(svg) {
34
+ return svg
35
+ .replace(/"/g, "'")
36
+ .replace(/[\r\n%#()<>?[\\\]^`{|}]/g, encodeURIComponent)
37
+ }
38
+
39
+ // Removes `width=…` and `height=…` only from the root <svg> opening tag.
40
+ // Child elements (e.g. <rect width="14"…>) are intentionally untouched.
41
+ // Handles double-quoted, single-quoted, and unquoted attribute values.
42
+ function stripDimensions(svg) {
43
+ return svg.replace(/<svg\b[^>]*>/i, (m) =>
44
+ m.replace(/\s(?:width|height)\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/g, '')
45
+ )
46
+ }
47
+
48
+ export async function generate({ input, output = '.', name = 'a-icon.shapes', internal = false } = {}) {
49
+ if (!input) throw new Error('generate-icons: --input is required')
50
+ const inDir = path.resolve(input)
51
+ const outDir = path.resolve(output)
52
+ if (!fs.existsSync(inDir)) throw new Error(`generate-icons: input folder not found: ${inDir}`)
53
+ fs.mkdirSync(outDir, { recursive: true })
54
+
55
+ const files = fs.readdirSync(inDir).filter((f) => f.endsWith('.svg')).sort()
56
+ if (files.length === 0) {
57
+ console.warn(`generate-icons: no .svg files in ${inDir}`)
58
+ }
59
+
60
+ const shapes = []
61
+ // Anta's per-element CSS lives in `@layer anta`. Consumer-generated
62
+ // icon sets default to the same so they sort alongside Anta's
63
+ // built-ins; consumers who want their icons unlayered can post-
64
+ // process the output.
65
+ const cssLines = [
66
+ '/* Auto-generated by scripts/generate-icons.mjs. Do not edit by hand. */',
67
+ '',
68
+ '@layer anta {',
69
+ '',
70
+ ]
71
+ // Built-in collision detection is for consumer runs only. Anta's own
72
+ // build passes --internal so it doesn't warn about every one of its
73
+ // own shapes "colliding" with itself.
74
+ const builtInSet = internal ? new Set() : new Set(DEFAULT_BUILT_IN)
75
+ const collisions = []
76
+
77
+ for (const file of files) {
78
+ const shape = path.parse(file).name
79
+ if (shapes.includes(shape)) {
80
+ console.warn(`generate-icons: duplicate shape name "${shape}" — skipping`)
81
+ continue
82
+ }
83
+ if (builtInSet.has(shape)) collisions.push(shape)
84
+ const raw = fs.readFileSync(path.join(inDir, file), 'utf8')
85
+ const encoded = encodeSvg(stripDimensions(raw)).trim()
86
+ cssLines.push(` a-icon[shape="${shape}"] {`)
87
+ cssLines.push(` --icon: url("data:image/svg+xml,${encoded}");`)
88
+ cssLines.push(' }')
89
+ cssLines.push('')
90
+ shapes.push(shape)
91
+ }
92
+
93
+ if (collisions.length > 0) {
94
+ console.warn(
95
+ `generate-icons: ${collisions.length} shape name(s) collide with Anta's built-in icons: ` +
96
+ `${collisions.join(', ')}. ` +
97
+ `The runtime CSS loaded last wins; import yours after \`@antadesign/anta/elements\` to override.`
98
+ )
99
+ }
100
+
101
+ // Optional synonyms — extra search keywords per shape. Read from
102
+ // `<input>/synonyms.json` if present; consumers/maintainers extend
103
+ // the list as their icon set grows. Format: { "shape-name": ["alt", "term"] }.
104
+ const synFile = path.join(inDir, 'synonyms.json')
105
+ /** @type {Record<string, string[]>} */
106
+ let synonyms = {}
107
+ if (fs.existsSync(synFile)) {
108
+ try {
109
+ const parsed = JSON.parse(fs.readFileSync(synFile, 'utf8'))
110
+ for (const [shape, list] of Object.entries(parsed)) {
111
+ if (!shapes.includes(shape)) {
112
+ console.warn(`generate-icons: synonyms.json references unknown shape "${shape}" — skipping`)
113
+ continue
114
+ }
115
+ if (!Array.isArray(list) || !list.every((v) => typeof v === 'string')) {
116
+ console.warn(`generate-icons: synonyms.json entry for "${shape}" is not a string array — skipping`)
117
+ continue
118
+ }
119
+ synonyms[shape] = list
120
+ }
121
+ } catch (e) {
122
+ console.warn(`generate-icons: failed to parse ${synFile}: ${e.message}`)
123
+ }
124
+ }
125
+
126
+ // Close the `@layer anta {` block opened at the top of cssLines.
127
+ cssLines.push('}')
128
+
129
+ const cssPath = path.join(outDir, `${name}.css`)
130
+ const tsPath = path.join(outDir, `${name}.ts`)
131
+ fs.writeFileSync(cssPath, cssLines.join('\n'))
132
+
133
+ const synonymsEntries = Object.entries(synonyms)
134
+ const tsLines = [
135
+ '/* Auto-generated by scripts/generate-icons.mjs. Do not edit by hand. */',
136
+ '',
137
+ 'export const ICON_SHAPES = [',
138
+ ...shapes.map((s) => ` '${s}',`),
139
+ "] as const",
140
+ '',
141
+ 'export const ICON_SYNONYMS: Readonly<Record<string, readonly string[]>> = {',
142
+ ...synonymsEntries.map(([shape, list]) =>
143
+ ` '${shape}': [${list.map((v) => `'${v.replace(/'/g, "\\'")}'`).join(', ')}],`
144
+ ),
145
+ '}',
146
+ '',
147
+ "declare module '@antadesign/anta' {",
148
+ ' interface IconShapes {',
149
+ ...shapes.map((s) => ` '${s}': true`),
150
+ ' }',
151
+ '}',
152
+ '',
153
+ "export type IconShape = keyof import('@antadesign/anta').IconShapes",
154
+ '',
155
+ ]
156
+ fs.writeFileSync(tsPath, tsLines.join('\n'))
157
+
158
+ console.log(`generate-icons: wrote ${shapes.length} shape${shapes.length === 1 ? '' : 's'} to ${cssPath} and ${tsPath}`)
159
+ return { shapes, cssPath, tsPath }
160
+ }
161
+
162
+ function parseArgs(argv) {
163
+ const out = {}
164
+ for (let i = 0; i < argv.length; i++) {
165
+ const arg = argv[i]
166
+ if (arg.startsWith('--')) {
167
+ const key = arg.slice(2)
168
+ const next = argv[i + 1]
169
+ if (next && !next.startsWith('--')) {
170
+ out[key] = next
171
+ i++
172
+ } else {
173
+ out[key] = true
174
+ }
175
+ }
176
+ }
177
+ return out
178
+ }
179
+
180
+ const isMain = import.meta.url === `file://${process.argv[1]}` ||
181
+ import.meta.url === fileURLToPath(import.meta.url + '')
182
+ if (isMain || process.argv[1]?.endsWith('generate-icons.mjs')) {
183
+ const args = parseArgs(process.argv.slice(2))
184
+ generate({
185
+ input: args.input,
186
+ output: args.output,
187
+ name: args.name,
188
+ internal: args.internal === true,
189
+ }).catch((e) => {
190
+ console.error(e.message)
191
+ process.exit(1)
192
+ })
193
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,36 @@
1
+ /**
2
+ * @antadesign/anta — Antithesis Design System
3
+ *
4
+ * Portable UI components with web component internals and JSX wrappers.
5
+ * Works with React, Preact (via compat or `configure()`), or any custom JSX runtime.
6
+ *
7
+ * Import components from this entry point:
8
+ * ```ts
9
+ * import { Progress } from '@antadesign/anta'
10
+ * ```
11
+ *
12
+ * Register custom elements (client-side only):
13
+ * ```ts
14
+ * import '@antadesign/anta/elements'
15
+ * ```
16
+ *
17
+ * @packageDocumentation
18
+ */
1
19
  export { Progress } from './components/Progress';
20
+ export type { ProgressProps } from './components/Progress';
21
+ export { Text } from './components/Text';
22
+ export type { TextProps } from './components/Text';
23
+ export { Icon } from './components/Icon';
24
+ export type { IconProps } from './components/Icon';
25
+ export { ICON_SHAPES, ICON_SYNONYMS } from './elements/a-icon.shapes';
26
+ export type { BaseProps, BaseAttributes } from './general_types';
2
27
  export { configure } from './jsx-runtime';
28
+ /**
29
+ * Seed interface for the icon shape registry. The generated
30
+ * `a-icon.shapes.d.ts` augments this interface with one key per
31
+ * available shape; consumers can do the same with their own generated
32
+ * .d.ts files (TypeScript merges them by interface name).
33
+ */
34
+ export interface IconShapes {
35
+ }
36
+ export type IconShape = keyof IconShapes;
package/dist/index.js CHANGED
@@ -1,6 +1,13 @@
1
1
  import { Progress } from "./components/Progress";
2
+ import { Text } from "./components/Text";
3
+ import { Icon } from "./components/Icon";
4
+ import { ICON_SHAPES, ICON_SYNONYMS } from "./elements/a-icon.shapes";
2
5
  import { configure } from "./jsx-runtime";
3
6
  export {
7
+ ICON_SHAPES,
8
+ ICON_SYNONYMS,
9
+ Icon,
4
10
  Progress,
11
+ Text,
5
12
  configure
6
13
  };
@@ -4,11 +4,25 @@ type JsxFunction = {
4
4
  h(type: ComponentType, props: Record<string, unknown> | null, ...children: unknown[]): unknown;
5
5
  }['h'];
6
6
  declare let _Fragment: ComponentType;
7
+ /**
8
+ * Swap the underlying JSX factory used by all anta components.
9
+ *
10
+ * Not needed for React or Preact-with-compat — those work automatically.
11
+ * Call this before rendering any anta components when using Preact without
12
+ * compat aliasing, or a custom JSX runtime.
13
+ *
14
+ * @example Preact without compat
15
+ * ```ts
16
+ * import { configure } from '@antadesign/anta'
17
+ * import { h, Fragment } from 'preact'
18
+ * configure(h, Fragment)
19
+ * ```
20
+ */
7
21
  export declare function configure(jsx: JsxFunction, Fragment?: ComponentType): void;
8
22
  export declare function jsx(type: ComponentType, props: Record<string, unknown> | null, key?: string | number): unknown;
9
23
  export declare function jsxs(type: ComponentType, props: Record<string, unknown> | null, key?: string | number): unknown;
10
24
  export { _Fragment as Fragment };
11
- import type { AProgressAttributes, BaseAttributes } from './general_types';
25
+ import type { AProgressAttributes, ATextAttributes, AIconAttributes, BaseAttributes } from './general_types';
12
26
  export declare namespace JSX {
13
27
  type IntrinsicElements = React.JSX.IntrinsicElements & {
14
28
  'a-progress': AProgressAttributes;
@@ -16,5 +30,7 @@ export declare namespace JSX {
16
30
  'a-progress-number': BaseAttributes;
17
31
  'a-progress-text': BaseAttributes;
18
32
  'a-progress-hint': BaseAttributes;
33
+ 'a-text': ATextAttributes;
34
+ 'a-icon': AIconAttributes;
19
35
  };
20
36
  }
package/dist/reset.css ADDED
@@ -0,0 +1,115 @@
1
+ /* Anta's reset + typography defaults. All rules live in `@layer anta`
2
+ so consumer-owned components (in `@layer components`) and utility
3
+ frameworks (in `@layer utilities`) can override on demand. The
4
+ layer-order declaration in `tokens.css` (`@layer base, anta,
5
+ components, utilities;`) keeps Anta above any preflight resets in
6
+ `@layer base` so a Tailwind import doesn't wipe Anta's typography. */
7
+
8
+ @layer anta {
9
+ /* ───── Structural reset ─────────────────────────────────────── */
10
+
11
+ /* Modern box-sizing — pads and borders count inside the declared
12
+ width/height. Universal selectors so `::before` / `::after`
13
+ pseudo-elements follow the host. */
14
+ *, *::before, *::after {
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ /* Zero out browser-default margins on every element. Apps build
19
+ up their own spacing from explicit values rather than fighting
20
+ the user-agent stylesheet. */
21
+ * {
22
+ margin: 0;
23
+ }
24
+
25
+ /* Replaced elements as block-level by default + cap to their
26
+ container so a misplaced large image doesn't blow out the
27
+ layout. Consumers re-inline them with `display: inline-block`
28
+ where needed. */
29
+ img, picture, video, canvas, svg {
30
+ display: block;
31
+ max-width: 100%;
32
+ }
33
+
34
+ /* Inherit typography into form controls — by default browsers
35
+ reset font on these. */
36
+ input, button, textarea, select {
37
+ font: inherit;
38
+ }
39
+
40
+ /* Wrap long words rather than overflow; `text-wrap: pretty`
41
+ balances widows on paragraphs, `text-wrap: balance` evens
42
+ headlines across lines. */
43
+ p, h1, h2, h3, h4, h5, h6 {
44
+ overflow-wrap: break-word;
45
+ }
46
+ p {
47
+ text-wrap: pretty;
48
+ }
49
+ h1, h2, h3, h4, h5, h6 {
50
+ text-wrap: balance;
51
+ }
52
+
53
+ /* ───── Typography defaults — Anta opinion ───────────────────── */
54
+
55
+ h1, h2, h3, h4, h5, h6 {
56
+ font-weight: 600;
57
+ }
58
+
59
+ strong {
60
+ font-weight: 600;
61
+ }
62
+
63
+ /* Prose lists (ul, ol) get a tight 3ch left padding so markers
64
+ hug the text, and items get a small bottom margin so
65
+ consecutive ones breathe. Markers are toned all the way down
66
+ to --text-5 since they are visual scaffolding, not content. */
67
+ ul, ol {
68
+ padding-left: 3ch;
69
+ }
70
+ :is(ul, ol) > li {
71
+ margin-bottom: 0.5em;
72
+ }
73
+ :is(ul, ol) > li::marker {
74
+ color: var(--text-5);
75
+ }
76
+
77
+ /* `<menu>` is for command/option lists, not prose. By default
78
+ browsers render it like a `<ul>` (disc markers, 40px padding);
79
+ strip all of that so it's a clean semantic container ready to
80
+ be styled. */
81
+ menu {
82
+ list-style: none;
83
+ padding: 0;
84
+ margin: 0;
85
+ }
86
+ menu > li {
87
+ margin: 0;
88
+ }
89
+
90
+ /* Global link defaults. Hairline underline at 75% alpha;
91
+ underline goes to currentColor (100%) and 1px on hover. Color
92
+ comes from --link-color tokens, which auto-flip in dark mode.
93
+ Components or specific contexts (nav, headings, buttons)
94
+ should override as needed. */
95
+ a, a:link, a:visited {
96
+ color: var(--link-color);
97
+ text-decoration: underline;
98
+ text-decoration-style: solid;
99
+ text-decoration-color: color-mix(in oklch, currentColor 75%, transparent);
100
+ text-decoration-thickness: 0.5px;
101
+ text-underline-offset: 3px;
102
+ }
103
+ a:hover {
104
+ color: var(--link-color-hover);
105
+ /* Underline color set to the hover token directly (rather than
106
+ `currentColor`) so it repaints in lockstep with `color` — a
107
+ few browsers latch onto the old currentColor value
108
+ otherwise. */
109
+ text-decoration-color: var(--link-color-hover);
110
+ text-decoration-thickness: 1px;
111
+ }
112
+ a:active {
113
+ text-decoration-color: color-mix(in oklch, currentColor 75%, transparent);
114
+ }
115
+ }