From 3b30f41974446f33493e9fb455ffa8794cc31d2b Mon Sep 17 00:00:00 2001 From: egdev6 Date: Sat, 30 May 2026 11:22:36 +0200 Subject: [PATCH] feat(harness): enforce prop default docs --- .atl/skills/_shared/component-contract.md | 4 +- .atl/skills/component-contributor/SKILL.md | 2 + .../references/git-workflow.md | 3 + .../references/stories.md | 1 + .atl/skills/components-auditor/SKILL.md | 2 + .github/PULL_REQUEST_TEMPLATE.md | 1 + .github/workflows/storybook-build.yml | 3 + .github/workflows/test.yml | 3 + lefthook.yml | 4 +- scripts/verify-prop-default-docs.mjs | 357 ++++++++++++++++++ src/components/atoms/calendar/types.ts | 10 +- src/components/atoms/checkbox/types.ts | 35 +- src/components/atoms/chip/types.ts | 52 ++- src/components/atoms/header/types.ts | 2 +- src/components/atoms/icon-button/types.ts | 5 +- src/components/atoms/input/types.ts | 5 +- src/components/atoms/popover/types.ts | 15 +- src/components/atoms/slider/types.ts | 2 +- src/components/atoms/table/types.ts | 102 +++++ src/components/atoms/tooltip/types.ts | 2 + src/components/molecules/breadcrumb/types.ts | 70 +++- 21 files changed, 660 insertions(+), 20 deletions(-) create mode 100644 scripts/verify-prop-default-docs.mjs diff --git a/.atl/skills/_shared/component-contract.md b/.atl/skills/_shared/component-contract.md index 6897fba3..53843afa 100644 --- a/.atl/skills/_shared/component-contract.md +++ b/.atl/skills/_shared/component-contract.md @@ -22,7 +22,9 @@ No extra component files unless a spec explicitly approves a composition split. - Use named component exports; avoid default component exports. - Reusable/systemic prop types live in `src/types`; component-local types stay component-specific. - `types.ts` imports `cva` and `VariantProps` from `class-variance-authority`; all CVA variants and `defaultVariants` live there. -- Public props exposed in stories include Storybook JSDoc annotations: `@control type` and `@default value` when a default exists. Prose descriptions are optional unless the public API needs clarification. +- Every public component prop declared through `types.ts` must have Storybook-facing JSDoc when it is story-exposed, including `@control type` where applicable. +- Every public prop with a runtime default must document the same default with `@default value` in `types.ts`. Runtime defaults include CVA `defaultVariants`, hook/component destructuring defaults, alias/coalesced fallbacks, and boolean fallback chains such as `disabled ?? isDisabled ?? false`. +- Run `node scripts/verify-prop-default-docs.mjs` before handoff; missing `@default` docs are a component contract violation. Prose descriptions are optional unless the public API needs clarification. - Hooks return explicit typed objects; never `any`. - No `Array` when `T[]` is equivalent. - No non-null assertions, `@ts-ignore`, or `@ts-expect-error` without a documented reason. diff --git a/.atl/skills/component-contributor/SKILL.md b/.atl/skills/component-contributor/SKILL.md index 6036b850..c2681516 100644 --- a/.atl/skills/component-contributor/SKILL.md +++ b/.atl/skills/component-contributor/SKILL.md @@ -118,6 +118,8 @@ Present a concise plan and wait for confirmation unless SDD delegation already a Implement files in this order: `types.ts` → hook → component → tests → stories → `index.ts`. +After hook/component defaults are known, return to `types.ts` and verify every public prop with a runtime default has matching JSDoc `@default` metadata. Run `node scripts/verify-prop-default-docs.mjs` before handoff. + Use `.atl/skills/_shared/component-contract.md` as the contract for each file. If a local mature component contradicts the shared contract, stop and ask whether to follow current code or update the contract. ### 4 — Explain decisions diff --git a/.atl/skills/component-contributor/references/git-workflow.md b/.atl/skills/component-contributor/references/git-workflow.md index e4e20857..70752735 100644 --- a/.atl/skills/component-contributor/references/git-workflow.md +++ b/.atl/skills/component-contributor/references/git-workflow.md @@ -133,6 +133,7 @@ The pre-PR review must check: - 6-file architecture and file responsibilities - CVA placement and TypeScript conventions - Storybook docs/controls/actions conventions +- Public prop JSDoc `@default` coverage matching runtime defaults (`node scripts/verify-prop-default-docs.mjs`) - Token usage and visual states - Accessibility, focus visibility, disabled behavior, and keyboard support - Tests and build evidence @@ -166,6 +167,7 @@ A PR will be rejected WITHOUT detailed feedback if: | Hardcoded colors/spacing | Must use design tokens from `theme.css` | | Missing tests | Unit tests AND story required | | Missing story | Storybook is the source of truth | +| Missing prop default docs | `types.ts` must match runtime defaults | | Container/Presentational mixed | Logic in `.tsx` file | --- @@ -179,6 +181,7 @@ A PR is ready to merge when: - [ ] No `any`, no `interface` - [ ] Tests in `.test.tsx` cover hook logic + component behavior - [ ] Story has args, controls, and description (NO play functions) +- [ ] Public prop defaults are documented in `types.ts` and `node scripts/verify-prop-default-docs.mjs` passes - [ ] No hardcoded values — only design tokens - [ ] ARIA attributes present and correct - [ ] PR template fully filled diff --git a/.atl/skills/component-contributor/references/stories.md b/.atl/skills/component-contributor/references/stories.md index fcca0391..887978a6 100644 --- a/.atl/skills/component-contributor/references/stories.md +++ b/.atl/skills/component-contributor/references/stories.md @@ -83,6 +83,7 @@ export const Default: Story = { - Does `meta.parameters.docs` avoid `description.component`? - Are examples aligned with the canonical component API and public docs style? - Does each story demonstrate one axis clearly? +- Do public props in `types.ts` document runtime defaults with `@default`, matching hook/component defaults and CVA `defaultVariants`? ## Notes diff --git a/.atl/skills/components-auditor/SKILL.md b/.atl/skills/components-auditor/SKILL.md index 46c208b0..4d58c237 100644 --- a/.atl/skills/components-auditor/SKILL.md +++ b/.atl/skills/components-auditor/SKILL.md @@ -40,6 +40,7 @@ Audit against `.atl/skills/_shared/component-contract.md`: - required 6-file pattern and no unapproved extra files; - TypeScript rules and public prop typing; +- Public prop JSDoc/default parity: compare `types.ts` props against CVA `defaultVariants`, hook/component destructuring defaults, alias/coalesced fallbacks, and boolean fallback chains; fail missing or stale `@default` docs. - CVA placement and hook/component responsibilities; - token usage and Tailwind v4 custom fractional naming; - named export and type export pattern; @@ -101,6 +102,7 @@ Verdict: - [SEVERITY] `file:line` — Problem: ... Expected: ... Found: ... Rule: ... ### Validation evidence +- `node scripts/verify-prop-default-docs.mjs`: pass/fail/not run — reason - `{command}`: pass/fail/not run — reason ``` diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 04fb337b..5a484e11 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -39,6 +39,7 @@ Closes # - [ ] Accessibility contract from the spec was implemented or deviations are explained below. - [ ] Follows the 6-file pattern: `types.ts`, `use*.ts`, `Component.tsx`, `Component.test.tsx`, `Component.stories.tsx`, `index.ts`. - [ ] CVA variants live in `types.ts`; JSX component has no state, CVA calls, or business logic. +- [ ] Public props with runtime defaults have matching `@default` JSDoc in `types.ts` (`node scripts/verify-prop-default-docs.mjs`). - [ ] Uses design tokens from `theme.css`; no hardcoded colors or arbitrary color utilities. - [ ] Storybook covers default, variants, documented states, edge cases, and dark mode when visually different. - [ ] Component audit result is PASS or accepted PASS WITH WARNINGS. diff --git a/.github/workflows/storybook-build.yml b/.github/workflows/storybook-build.yml index a824df21..b8becda0 100644 --- a/.github/workflows/storybook-build.yml +++ b/.github/workflows/storybook-build.yml @@ -28,6 +28,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Verify prop default docs + run: node scripts/verify-prop-default-docs.mjs + - name: Build Storybook run: pnpm storybook-build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d0ca33f..f1331ef0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,5 +32,8 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Verify prop default docs + run: node scripts/verify-prop-default-docs.mjs + - name: Run tests run: pnpm test diff --git a/lefthook.yml b/lefthook.yml index acaed443..5809e698 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -5,6 +5,8 @@ pre-commit: glob: "*.{ts,tsx,js,jsx,json,css}" run: pnpm exec biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files} stage_fixed: true + prop-docs: + run: "node scripts/verify-prop-default-docs.mjs" typecheck: run: "pnpm exec tsc --noEmit" commit-msg: @@ -15,4 +17,4 @@ pre-push: parallel: false commands: test: - run: "pnpm exec vitest run --run" \ No newline at end of file + run: "pnpm exec vitest run --run" diff --git a/scripts/verify-prop-default-docs.mjs b/scripts/verify-prop-default-docs.mjs new file mode 100644 index 00000000..d75dc456 --- /dev/null +++ b/scripts/verify-prop-default-docs.mjs @@ -0,0 +1,357 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import ts from 'typescript'; + +const componentsRoot = path.resolve('src/components'); +const ignoredRuntimeDefaultValues = new Set(['undefined', 'null']); + +const findComponentTypeFiles = (dir) => { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + const files = []; + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...findComponentTypeFiles(fullPath)); + continue; + } + + if (entry.name === 'types.ts') { + files.push(fullPath); + } + } + + return files; +}; + +const getPropName = (name, sourceFile) => { + if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) { + return name.text; + } + + return name.getText(sourceFile); +}; + +const normalizeDefault = (value) => { + const trimmed = String(value ?? '') + .trim() + .split(/\r?\n/)[0] + .trim() + .replace(/\s+/g, ' '); + + if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || (trimmed.startsWith('"') && trimmed.endsWith('"'))) { + return trimmed.slice(1, -1); + } + + return trimmed; +}; + +const getDefaultTagValue = (member, sourceFile) => { + const defaultTag = ts.getJSDocTags(member).find((tag) => tag.tagName.getText(sourceFile) === 'default'); + if (!defaultTag) { + return undefined; + } + + if (typeof defaultTag.comment === 'string') { + return normalizeDefault(defaultTag.comment); + } + + return normalizeDefault( + defaultTag.comment + ?.map((part) => part.text) + .join('') + .trim() ?? '' + ); +}; + +const collectPropsFromTypeNode = (typeNode, aliases, sourceFile, visited = new Set()) => { + const props = new Map(); + + const visitType = (node) => { + if (ts.isTypeLiteralNode(node)) { + for (const member of node.members) { + if (ts.isPropertySignature(member) && member.name) { + props.set(getPropName(member.name, sourceFile), { + defaultValue: getDefaultTagValue(member, sourceFile), + pos: member.getStart(sourceFile), + text: member.getText(sourceFile) + }); + } + } + return; + } + + if (ts.isIntersectionTypeNode(node)) { + for (const child of node.types) { + visitType(child); + } + return; + } + + if (ts.isParenthesizedTypeNode(node)) { + visitType(node.type); + return; + } + + if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) { + const aliasName = node.typeName.text; + if (visited.has(aliasName)) { + return; + } + + const aliased = aliases.get(aliasName); + if (!aliased) { + return; + } + + visited.add(aliasName); + visitType(aliased); + } + }; + + visitType(typeNode); + return props; +}; + +const collectPublicPropDocs = (typesPath) => { + const source = fs.readFileSync(typesPath, 'utf8'); + const sourceFile = ts.createSourceFile(typesPath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + const aliases = new Map(); + const rootPropAliases = []; + + for (const statement of sourceFile.statements) { + if (!ts.isTypeAliasDeclaration(statement)) { + continue; + } + + aliases.set(statement.name.text, statement.type); + + const isExported = statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false; + if (isExported && /Props$/.test(statement.name.text)) { + rootPropAliases.push(statement); + } + } + + const propDocs = new Map(); + for (const alias of rootPropAliases) { + const props = collectPropsFromTypeNode(alias.type, aliases, sourceFile); + for (const [name, info] of props) { + propDocs.set(name, info); + } + } + + return propDocs; +}; + +const getLiteralValue = (node, _sourceFile, constDefaults = new Map()) => { + if (ts.isIdentifier(node) && constDefaults.has(node.text)) { + return constDefaults.get(node.text); + } + + if (ts.isStringLiteral(node) || ts.isNumericLiteral(node)) { + return node.text; + } + + if (node.kind === ts.SyntaxKind.TrueKeyword) { + return 'true'; + } + + if (node.kind === ts.SyntaxKind.FalseKeyword) { + return 'false'; + } + + if (ts.isArrayLiteralExpression(node) && node.elements.length === 0) { + return '[]'; + } + + if (ts.isObjectLiteralExpression(node) && node.properties.length === 0) { + return '{}'; + } + + return undefined; +}; + +const shouldScanRuntimeFile = (fileName) => { + if (!/\.[tj]sx?$/.test(fileName)) { + return false; + } + + if (/\.(stories|test)\.[tj]sx?$/.test(fileName)) { + return false; + } + + return /^use.+\.ts$/.test(fileName) || /^[A-Z][^.]*\.tsx$/.test(fileName); +}; + +const flattenNullishCoalescing = (node) => { + if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) { + return [...flattenNullishCoalescing(node.left), ...flattenNullishCoalescing(node.right)]; + } + + return [node]; +}; + +const collectRuntimeDefaults = (componentDir, typesPath) => { + const defaults = new Map(); + const sourcePaths = fs + .readdirSync(componentDir) + .filter(shouldScanRuntimeFile) + .map((fileName) => path.join(componentDir, fileName)); + + sourcePaths.push(typesPath); + + for (const sourcePath of sourcePaths) { + const source = fs.readFileSync(sourcePath, 'utf8'); + const sourceFile = ts.createSourceFile( + sourcePath, + source, + ts.ScriptTarget.Latest, + true, + sourcePath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS + ); + const constDefaults = new Map(); + + const recordDefault = (propName, value, node, source = 'runtime') => { + if (value === undefined) { + return; + } + + defaults.set(propName, { + value: normalizeDefault(value), + file: path.relative(process.cwd(), sourcePath), + line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1, + source + }); + }; + + const collectConstDefaults = (node) => { + if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) { + const value = getLiteralValue(node.initializer, sourceFile, constDefaults); + if (value !== undefined) { + constDefaults.set(node.name.text, value); + } + } + + ts.forEachChild(node, collectConstDefaults); + }; + + const visit = (node) => { + if (ts.isBindingElement(node) && node.initializer) { + const nameNode = node.propertyName ?? node.name; + if (ts.isIdentifier(nameNode) || ts.isStringLiteral(nameNode) || ts.isNumericLiteral(nameNode)) { + recordDefault( + getPropName(nameNode, sourceFile), + getLiteralValue(node.initializer, sourceFile, constDefaults), + node + ); + } + } + + if (ts.isPropertyAssignment(node) && node.name && getPropName(node.name, sourceFile) === 'defaultVariants') { + if (ts.isObjectLiteralExpression(node.initializer)) { + for (const property of node.initializer.properties) { + if (ts.isPropertyAssignment(property) && property.name) { + recordDefault( + getPropName(property.name, sourceFile), + getLiteralValue(property.initializer, sourceFile, constDefaults), + property, + 'cva defaultVariants' + ); + } + } + } + } + + if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) { + if (ts.isParenthesizedExpression(node.parent) && ts.isConditionalExpression(node.parent.parent)) { + ts.forEachChild(node, visit); + return; + } + + const operands = flattenNullishCoalescing(node); + const fallback = operands.at(-1); + const fallbackValue = fallback + ? (getLiteralValue(fallback, sourceFile, constDefaults) ?? + (ts.isIdentifier(fallback) ? defaults.get(fallback.text)?.value : undefined)) + : undefined; + + if (fallbackValue && !ignoredRuntimeDefaultValues.has(fallbackValue)) { + for (const operand of operands.slice(0, -1)) { + if (ts.isIdentifier(operand)) { + recordDefault(operand.text, fallbackValue, node, 'nullish fallback'); + } + } + } + } + + ts.forEachChild(node, visit); + }; + + collectConstDefaults(sourceFile); + visit(sourceFile); + } + + return defaults; +}; + +const failures = []; + +for (const typesPath of findComponentTypeFiles(componentsRoot)) { + const componentDir = path.dirname(typesPath); + const propDocs = collectPublicPropDocs(typesPath); + const runtimeDefaults = collectRuntimeDefaults(componentDir, typesPath); + + for (const [propName, runtimeDefault] of runtimeDefaults) { + const propDoc = propDocs.get(propName); + if (!propDoc || ignoredRuntimeDefaultValues.has(runtimeDefault.value)) { + continue; + } + + if (runtimeDefault.source === 'cva defaultVariants' && /React(?:\.React)?Node|ReactElement/.test(propDoc.text)) { + continue; + } + + if (propDoc.defaultValue === undefined) { + failures.push({ + kind: 'missing', + component: path.relative(process.cwd(), componentDir), + propName, + expectedValue: runtimeDefault.value, + source: `${runtimeDefault.file}:${runtimeDefault.line}`, + typesPath: path.relative(process.cwd(), typesPath) + }); + continue; + } + + if (propDoc.defaultValue !== runtimeDefault.value) { + failures.push({ + kind: 'mismatch', + component: path.relative(process.cwd(), componentDir), + propName, + actualValue: propDoc.defaultValue, + expectedValue: runtimeDefault.value, + source: `${runtimeDefault.file}:${runtimeDefault.line}`, + typesPath: path.relative(process.cwd(), typesPath) + }); + } + } +} + +if (failures.length > 0) { + console.error('Public component props with runtime defaults must document matching @default values in types.ts.'); + for (const failure of failures) { + if (failure.kind === 'missing') { + console.error( + `- ${failure.typesPath}: prop "${failure.propName}" defaults to ${failure.expectedValue} at ${failure.source} but has no @default tag` + ); + continue; + } + + console.error( + `- ${failure.typesPath}: prop "${failure.propName}" documents @default ${failure.actualValue} but runtime default is ${failure.expectedValue} at ${failure.source}` + ); + } + process.exit(1); +} + +console.log('Prop default docs verified.'); diff --git a/src/components/atoms/calendar/types.ts b/src/components/atoms/calendar/types.ts index 90bab877..742494df 100644 --- a/src/components/atoms/calendar/types.ts +++ b/src/components/atoms/calendar/types.ts @@ -855,7 +855,10 @@ export type CalendarProps = { /** @control object */ selectedDate?: CalendarSelection; onDateChange?: (date: CalendarSelection) => void; - /** @control object */ + /** + * @control object + * @default [] + */ disabledDates?: Date[]; /** * @control select @@ -902,7 +905,10 @@ export type CalendarProps = { * @default light */ theme?: 'light' | 'dark'; - /** @control object */ + /** + * @control object + * @default [] + */ highlightedDates?: CalendarHighlightedDate[]; /** * @control text diff --git a/src/components/atoms/checkbox/types.ts b/src/components/atoms/checkbox/types.ts index 211b0117..7d234ebd 100644 --- a/src/components/atoms/checkbox/types.ts +++ b/src/components/atoms/checkbox/types.ts @@ -306,21 +306,42 @@ type CheckboxCommonProps = { ariaDescribedBy?: string | string[]; /** @control text */ 'aria-describedby'?: string; - /** @control select */ + /** + * @control select + * @default default + */ variant?: CheckboxVariant; - /** @control select */ + /** + * @control select + * @default md + */ size?: CheckboxSize; /** @control boolean */ checked?: boolean; - /** @control boolean */ + /** + * @control boolean + * @default false + */ defaultChecked?: boolean; - /** @control boolean */ + /** + * @control boolean + * @default false + */ indeterminate?: boolean; - /** @control boolean */ + /** + * @control boolean + * @default false + */ disabled?: boolean; - /** @control boolean */ + /** + * @control boolean + * @default false + */ readOnly?: boolean; - /** @control boolean */ + /** + * @control boolean + * @default false + */ invalid?: boolean; /** @control action */ onChange?: (checked: boolean, event: ChangeEvent) => void; diff --git a/src/components/atoms/chip/types.ts b/src/components/atoms/chip/types.ts index 2a8b6f57..05ce01b8 100644 --- a/src/components/atoms/chip/types.ts +++ b/src/components/atoms/chip/types.ts @@ -216,14 +216,38 @@ export type ChipColorVariants = VariantProps['color']; export type ChipSizeVariants = VariantProps['size']; type ChipCustomProps = { + /** @control text */ children?: React.ReactNode; + /** + * @control select + * @default solid + */ variant?: ChipVariant; + /** + * @control select + * @default primary + */ color?: ChipColorVariants; + /** + * @control select + * @default md + */ size?: ChipSizeVariants; + /** + * @control select + * @default full + */ radius?: RadiusSize; + /** + * @control select + * @default default + */ animation?: Animation; + /** @control object */ avatar?: React.ReactNode; + /** @control object */ startContent?: React.ReactNode; + /** @control object */ endContent?: React.ReactNode; /** * Preferred root element for the chip. @@ -231,20 +255,46 @@ type ChipCustomProps = { * Note: when `closable` and interactive are combined, the chip is * rendered as a split-actions group (`div[role="group"]` with * two sibling buttons) to avoid nested interactive controls. + * + * @control select */ as?: 'div' | 'button'; onClick?: React.MouseEventHandler; + /** + * @control boolean + * @default false + */ disabled?: boolean; - /** @deprecated Use `disabled` instead. */ + /** + * @deprecated Use `disabled` instead. + * @control boolean + * @default false + */ isDisabled?: boolean; + /** + * @control boolean + * @default false + */ closable?: boolean; onClose?: (event: React.MouseEvent | React.KeyboardEvent) => void; + /** + * @control boolean + * @default false + */ selectable?: boolean; + /** @control boolean */ selected?: boolean; + /** + * @control boolean + * @default false + */ defaultSelected?: boolean; onSelectedChange?: (selected: boolean) => void; + /** @control text */ className?: string; + /** @control object */ classNames?: Partial>; + /** @control text */ ariaLabel?: string; }; diff --git a/src/components/atoms/header/types.ts b/src/components/atoms/header/types.ts index bae07429..0e34c5cd 100644 --- a/src/components/atoms/header/types.ts +++ b/src/components/atoms/header/types.ts @@ -61,7 +61,7 @@ export type HeaderProps = Omit & size?: HeaderSize; /** * @control select - * @default undefined + * @default h1 */ fontSize?: HeaderSize; /** diff --git a/src/components/atoms/icon-button/types.ts b/src/components/atoms/icon-button/types.ts index eb32e3c7..67138d88 100644 --- a/src/components/atoms/icon-button/types.ts +++ b/src/components/atoms/icon-button/types.ts @@ -119,7 +119,10 @@ export type IconButtonProps = NativeIconButtonProps & * @default primary */ variant?: IconButtonVariantProps['variant']; - /** @control text */ + /** + * @control text + * @default image + */ icon?: DynamicIconName; /** * @control select diff --git a/src/components/atoms/input/types.ts b/src/components/atoms/input/types.ts index af989143..e48ae03d 100644 --- a/src/components/atoms/input/types.ts +++ b/src/components/atoms/input/types.ts @@ -214,7 +214,10 @@ export type InputProps = NativeInputProps & { * @default false */ rounded?: boolean; - /** @control text */ + /** + * @control text + * @default '' + */ label?: string; /** * @control boolean diff --git a/src/components/atoms/popover/types.ts b/src/components/atoms/popover/types.ts index 97d76189..8fe5de7e 100644 --- a/src/components/atoms/popover/types.ts +++ b/src/components/atoms/popover/types.ts @@ -199,7 +199,10 @@ export type PopoverProps = { children: ReactNode; /** @control boolean */ open?: boolean; - /** @control boolean */ + /** + * @control boolean + * @default false + */ defaultOpen?: boolean; onOpenChange?: (open: boolean) => void; }; @@ -207,9 +210,15 @@ export type PopoverProps = { export type PopoverTriggerProps = { /** @control object */ children: ReactNode; - /** @control boolean */ + /** + * @control boolean + * @default true + */ asChild?: boolean; - /** @control boolean */ + /** + * @control boolean + * @default false + */ disabled?: boolean; /** @control text */ className?: string; diff --git a/src/components/atoms/slider/types.ts b/src/components/atoms/slider/types.ts index a8a27617..77b7c449 100644 --- a/src/components/atoms/slider/types.ts +++ b/src/components/atoms/slider/types.ts @@ -147,7 +147,7 @@ export type SliderProps = NativeSliderProps & { rounded?: SliderRounded; /** @control boolean @default true */ fullWidth?: NonNullable; - /** Convenience accessible name for the single thumb. @control text */ + /** Convenience accessible name for the single thumb. @control text @default Slider value */ ariaLabel?: string; /** Distinguishable accessible names for explicit two-thumb range mode. @control object */ thumbLabels?: [string, string]; diff --git a/src/components/atoms/table/types.ts b/src/components/atoms/table/types.ts index ff02fe4f..eb19319b 100644 --- a/src/components/atoms/table/types.ts +++ b/src/components/atoms/table/types.ts @@ -248,43 +248,145 @@ export type TableEvents = { type NativeTableProps = Omit, 'children' | 'className'>; export type TableProps = NativeTableProps & { + /** @control object */ items?: T[]; + /** + * @control object + * @default [] + */ columns?: TableColumn[]; + /** + * @control select + * @default auto + */ layout?: TableLayout; + /** + * @control select + * @default md + */ radius?: TableRadius; + /** + * @control select + * @default sm + */ shadow?: TableShadow; + /** + * @control boolean + * @default false + */ hideHeader?: boolean; + /** + * @control boolean + * @default false + */ isStriped?: boolean; + /** + * @control boolean + * @default false + */ isCompact?: boolean; + /** + * @control boolean + * @default false + */ isHeaderSticky?: boolean; + /** + * @control boolean + * @default true + */ fullWidth?: boolean; + /** + * @control boolean + * @default false + */ removeWrapper?: boolean; + /** + * @control boolean + * @default false + */ showSelectionCheckboxes?: boolean; + /** @control object */ sortDescriptor?: SortDescriptor | null; + /** @control object */ selectedKeys?: Selection; + /** @control object */ defaultSelectedKeys?: Selection; + /** @control object */ disabledKeys?: Selection; + /** + * @control boolean + * @default false + */ disallowEmptySelection?: boolean; + /** + * @control select + * @default none + */ selectionMode?: SelectionMode; + /** + * @control boolean + * @default false + */ isKeyboardNavigationDisabled?: boolean; + /** + * @control object + * @default {} + */ classNames?: TableClassNames; + /** + * @control object + * @default [] + */ data?: T[]; + /** + * @control boolean + * @default false + */ loading?: boolean; + /** + * @control text + * @default No data available + */ emptyContent?: React.ReactNode; onRowClick?: (row: T) => void; rowKey?: (row: T) => React.Key; getRowLabel?: (row: T) => string; + /** + * @control select + * @default default + */ variant?: TableVariant; + /** + * @control select + * @default md + */ size?: TableSize; + /** @control text */ className?: string; + /** + * @control boolean + * @default false + */ pagination?: boolean; + /** + * @control number + * @default 10 + */ pageSize?: number; + /** @control number */ totalRows?: number; onPageChange?: (page: number) => void; + /** + * @control select + * @default false + */ rowSelection?: 'single' | 'multiple' | false; + /** @control object */ selectedRows?: T[]; onSelectRows?: (selectedRows: T[]) => void; + /** @control text */ ariaLabel?: string; + /** @control text */ ariaLabelledBy?: string; }; diff --git a/src/components/atoms/tooltip/types.ts b/src/components/atoms/tooltip/types.ts index 3b8e6d47..d61c106c 100644 --- a/src/components/atoms/tooltip/types.ts +++ b/src/components/atoms/tooltip/types.ts @@ -79,6 +79,7 @@ export type TooltipProps = Omit, 'children' | 'color' | ' /** * Legacy alias for `position`. * @control select + * @default top */ placement?: TooltipPosition; /** @@ -89,6 +90,7 @@ export type TooltipProps = Omit, 'children' | 'color' | ' /** * Legacy alias for `delayMs`. * @control number + * @default 0 */ delayShow?: number; /** diff --git a/src/components/molecules/breadcrumb/types.ts b/src/components/molecules/breadcrumb/types.ts index 26c54fc2..5fad9df4 100644 --- a/src/components/molecules/breadcrumb/types.ts +++ b/src/components/molecules/breadcrumb/types.ts @@ -52,28 +52,96 @@ export type BreadcrumbItem = { endContent?: BreadcrumbContent; }; -export type BreadcrumbProps = BreadcrumbVariants & { +export type BreadcrumbProps = Omit & { + /** @control text */ 'aria-label'?: string; + /** @control text */ containerClassName?: string; + /** @control object */ collapsedElement?: ReactNode; + /** @control object */ endContent?: BreadcrumbContent; + /** + * @control boolean + * @default false + */ hideSeparator?: boolean; + /** + * @control object + * @default more-horizontal + */ iconCollapse?: BreadcrumbContent; + /** + * @control number + * @default 18 + */ iconSizes?: IconSizes; + /** + * @control boolean + * @default false + */ isNavigationDisabled?: boolean; + /** @control object */ items: BreadcrumbItem[]; + /** + * @control number + * @default 1 + */ itemsAfterCollapse?: number; + /** + * @control number + * @default 1 + */ itemsBeforeCollapse?: number; + /** @control text */ linkClassName?: string; + /** + * @control number + * @default 0 + */ maxItem?: number; + /** + * @control number + * @default 0 + */ maxItems?: number; onCollapsedClick?: (hiddenItems: BreadcrumbItem[]) => void; onItemClick?: (item: BreadcrumbItem, index: number) => void; + /** + * @control select + * @default md + */ radius?: BreadcrumbRounded; + /** + * @control select + * @default md + */ + rounded?: BreadcrumbRounded; + /** + * @control select + * @default md + */ + size?: BreadcrumbVariants['size']; + /** + * @control select + * @default regular + */ + variant?: BreadcrumbVariants['variant']; + /** + * @control text + * @default / + */ separator?: ReactNode; + /** @control text */ separatorClassName?: string; + /** + * @control boolean + * @default false + */ showTooltip?: boolean; + /** @control object */ startContent?: BreadcrumbContent; + /** @control text */ textClassName?: string; };