diff --git a/CONTEXT.md b/CONTEXT.md index 969cb8f3..d02e93f4 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -11,8 +11,8 @@ devs use it directly. _Examples_: `Text`, `Link`, `Icon`, `LoadingSpinner`, `Hea `Numeral`. _Avoid_: calling these "primitives" in source comments — that name is reserved. **Composed**: A component that combines two or more atoms or primitives into an opinionated, -ready-to-drop-in unit aimed at app devs. _Examples_: `Button`, `IconButton`, `CloseButton`, -`TextField`, `ComboboxField`. +ready-to-drop-in unit aimed at app devs. _Examples_: `Button`, `IconButton`, `TextField`, +`ComboboxField`. **Primitive**: A building block whose audience is library authors assembling the next composed component, not app devs. May be a single file (e.g. `text-field/primitive`) or a kit of parts (e.g. diff --git a/README.md b/README.md index f7caeaea..a1980487 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,6 @@ Components to build: - [ ] ActionsDropdown - [ ] BulkActionBar - [x] Button -- [x] CloseButton - [x] IconButton ### Forms diff --git a/apps/docs/content/docs/components/actions/close-button.mdx b/apps/docs/content/docs/components/actions/close-button.mdx deleted file mode 100644 index 947cfa73..00000000 --- a/apps/docs/content/docs/components/actions/close-button.mdx +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Close Button -description: Icon button preset for close and dismiss actions. ---- - -import { story } from '../../../../src/close-button/close-button.story'; - - - -../../../../../../packages/@luke-ui/react/docs/close-button.md diff --git a/apps/docs/content/docs/components/actions/meta.json b/apps/docs/content/docs/components/actions/meta.json index 86f9a753..87422c15 100644 --- a/apps/docs/content/docs/components/actions/meta.json +++ b/apps/docs/content/docs/components/actions/meta.json @@ -1,4 +1,4 @@ { "title": "Actions", - "pages": ["button", "close-button", "icon-button", "link"] + "pages": ["button", "icon-button", "link"] } diff --git a/apps/docs/src/close-button/close-button.story.tsx b/apps/docs/src/close-button/close-button.story.tsx deleted file mode 100644 index 9de86d01..00000000 --- a/apps/docs/src/close-button/close-button.story.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { defineStoryFactory } from '@fumadocs/story/vite/client'; -import type { CloseButtonProps } from '@luke-ui/react/close-button'; -import { CloseButton } from '@luke-ui/react/close-button'; -import { StoryWrapper } from '../lib/story-wrapper'; - -const { defineStory } = defineStoryFactory(); - -type CloseButtonStoryProps = Pick; - -function CloseButtonPlayground(props: CloseButtonStoryProps) { - return ( - - - - ); -} - -export const story = defineStory({ - args: { - initial: {}, - }, - Component: CloseButtonPlayground, -}); diff --git a/docs/CONVENTIONS.md b/docs/CONVENTIONS.md index 475fe8cf..87f84b83 100644 --- a/docs/CONVENTIONS.md +++ b/docs/CONVENTIONS.md @@ -12,7 +12,7 @@ Managed by `oxfmt`. Tabs, 2 width, 80 width, single quotes (TS), double quotes ( - **Components**: `PascalCase` (`Button.tsx`) - **Props**: `PascalCaseProps` (`ButtonProps`) -- **Files**: `kebab-case` (`close-button.tsx`) +- **Files**: `kebab-case` (`icon-button.tsx`) - **CSS**: `*.css.ts` - **Stories**: `*.stories.tsx` @@ -34,7 +34,7 @@ Components follow a three-tier taxonomy (see `CONTEXT.md` for full definitions): - **Atom** — single conceptual unit used directly by app devs (`Text`, `Link`, `Icon`, `Heading`, `Numeral`, `Emoji`, `LoadingSpinner`). Gets a doc page. - **Composed** — combines atoms/primitives into a ready-to-drop-in pattern (`Button`, `IconButton`, - `CloseButton`, `TextField`, `ComboboxField`). Gets a doc page. + `TextField`, `ComboboxField`). Gets a doc page. - **Primitive** — building block for library authors only; documented in package docs but not in hosted docs. May be a single file (e.g. `text-input`) or a multi-file kit (e.g. `combobox/*`, `field/*`). diff --git a/docs/adr/0001-component-tier-taxonomy.md b/docs/adr/0001-component-tier-taxonomy.md index 2b57ba94..46b07bc6 100644 --- a/docs/adr/0001-component-tier-taxonomy.md +++ b/docs/adr/0001-component-tier-taxonomy.md @@ -3,10 +3,10 @@ Components are classified into three tiers — **atom**, **composed**, and **primitive** — which determine whether a component gets a public doc page. - **Atoms** (`Text`, `Link`, `Icon`, `LoadingSpinner`, `Heading`, `Emoji`, `Numeral`) present as a single conceptual unit; app devs use them directly. They get docs. -- **Composed** (`Button`, `IconButton`, `CloseButton`, `TextField`, `ComboboxField`) combine atoms or primitives into a ready-to-drop-in pattern for app devs. They get docs. +- **Composed** (`Button`, `IconButton`, `TextField`, `ComboboxField`) combine atoms or primitives into a ready-to-drop-in pattern for app devs. They get docs. - **Primitives** (`TextInput`, the `Combobox*` kit, `button/primitive`, the `Field` kit) exist for library authors building the next composed component. They do not get hosted docs (app-dev surface), but they are documented in package docs so library authors and coding agents can reach them. See ADR-0003. -We rejected a two-tier "composed vs primitive" model because it couldn't classify atoms cleanly — `Text` is labeled "primitive" in its own JSDoc but is nothing like `TextInput` in terms of who reaches for it. We also rejected classifying the composed `Field` as doc-worthy: although it composes other components, its audience is library authors (it is always wrapped by a `*-Field`), not app devs. +We rejected a two-tier "composed vs primitive" model because it couldn't classify atoms cleanly — `Text` is labeled "primitive" in its own JSDoc but is nothing like `TextInput` in terms of who reaches for it. We also rejected classifying the composed `Field` as doc-worthy: although it composes other components, its audience is library authors (it is always wrapped by a `*-Field`), not app devs. Thin presets of a single composed component (e.g. the removed `CloseButton`, which only pinned `IconButton`'s `icon` and `aria-label`) don't earn a Composed-tier module of their own — they fail the deletion test, and the pattern belongs in the wrapped component's docs instead (see issue #41). The rule in one line: **hosted docs target app developers; package docs cover every public export so library authors and agents can reach them.** diff --git a/docs/adr/0006-docs-md-structure-standard.md b/docs/adr/0006-docs-md-structure-standard.md index 8e111bbc..fde16b3a 100644 --- a/docs/adr/0006-docs-md-structure-standard.md +++ b/docs/adr/0006-docs-md-structure-standard.md @@ -26,8 +26,8 @@ all while every other component used one `##` per feature, and nothing flagged i do not sort alphabetically and do not default to the order props happen to be declared in the TypeScript interface. 4. **`## Accessibility`** — scaffolded by default for every new component. Delete it if the component - has nothing beyond default semantics to call out (e.g. `close-button.docs.md` has none because - `CloseButton` hard-codes its `aria-label`). + has nothing beyond default semantics to call out (e.g. `text.docs.md` has none because `Text` + renders plain text with default semantics). 5. **Cross-reference sections, always last** — - `## Primitive {Name}` when a composed component has a same-subpath single-component primitive counterpart, where `{Name}` is the primitive's own exported name (e.g. `## Primitive Button` in diff --git a/packages/@luke-ui/docs-tools/src/__tests__/discover-exports.test.ts b/packages/@luke-ui/docs-tools/src/__tests__/discover-exports.test.ts index 030a69cb..57324b3b 100644 --- a/packages/@luke-ui/docs-tools/src/__tests__/discover-exports.test.ts +++ b/packages/@luke-ui/docs-tools/src/__tests__/discover-exports.test.ts @@ -8,7 +8,7 @@ describe('discoverExports', () => { it('classifies component-shaped exports as component shape', () => { const exports = { './button': './dist/button/index.js', - './close-button': './dist/close-button/index.js', + './icon-button': './dist/icon-button/index.js', }; const result = discoverExports(exports); expect(result.find((e) => e.path === './button')?.shape).toBe('component'); diff --git a/packages/@luke-ui/react/package.json b/packages/@luke-ui/react/package.json index 13408ffd..22040f31 100644 --- a/packages/@luke-ui/react/package.json +++ b/packages/@luke-ui/react/package.json @@ -16,7 +16,6 @@ "exports": { "./button": "./dist/button/index.js", "./button/primitive": "./dist/button/primitive/index.js", - "./close-button": "./dist/close-button/index.js", "./combobox-field": "./dist/combobox-field/index.js", "./combobox-field/primitive": "./dist/combobox-field/primitive/index.js", "./emoji": "./dist/emoji/index.js", diff --git a/packages/@luke-ui/react/src/close-button/close-button.docs.md b/packages/@luke-ui/react/src/close-button/close-button.docs.md deleted file mode 100644 index 2dd6043c..00000000 --- a/packages/@luke-ui/react/src/close-button/close-button.docs.md +++ /dev/null @@ -1,37 +0,0 @@ -```tsx - -``` - -`CloseButton` renders a close icon with `tone="ghost"` by default, and a fixed -`aria-label="Close"`. All `IconButton` props except `icon` are forwarded. - -## Best Practices - -| Guidance | Practices | -| -------- | ------------------------------------------------------------------------------------------- | -| Do | Use `CloseButton` to dismiss dialogs, panels, drawers, and toasts. | -| Don't | Use `CloseButton` somewhere "Close" doesn't describe the action — use `IconButton` instead. | - -## Tone - -```tsx - -``` - -## Size - -```tsx - -``` - -## Accessibility - -The accessible name is fixed to "Close" — an `aria-label` passed to -`CloseButton` is accepted by the type but ignored at render. Use `IconButton` -directly if you need a different label. - -## When to use vs IconButton - -`CloseButton` is an `IconButton` preset for the close icon with a fixed label. -Use `IconButton` directly for any other icon, or when you need a custom -`aria-label`. diff --git a/packages/@luke-ui/react/src/close-button/close-button.stories.tsx b/packages/@luke-ui/react/src/close-button/close-button.stories.tsx deleted file mode 100644 index 47ec2611..00000000 --- a/packages/@luke-ui/react/src/close-button/close-button.stories.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import type { CloseButtonProps } from '@luke-ui/react/close-button'; -import { CloseButton } from '@luke-ui/react/close-button'; -import { expect } from 'storybook/test'; -import preview from '../../.storybook/preview.js'; - -const meta = preview.meta({ - component: CloseButton, - tags: ['actions'], - title: 'Actions/CloseButton', -}); - -const sizes: Array> = ['small', 'medium']; - -const flexWrapStyle = { - display: 'flex', - flexWrap: 'wrap', - gap: '1rem', -} as const; - -export const Default = meta.story({ - play: async ({ canvas }) => { - await expect(canvas.getByRole('button', { name: 'Close' })).toBeInTheDocument(); - }, -}); - -export const Sizes = meta.story({ - render: (props) => ( -
- {sizes.map((size) => ( - - ))} -
- ), -}); - -export const Disabled = meta.story({ - args: { - isDisabled: true, - }, - render: (props) => ( -
- {sizes.map((size) => ( - - ))} -
- ), -}); diff --git a/packages/@luke-ui/react/src/close-button/index.tsx b/packages/@luke-ui/react/src/close-button/index.tsx deleted file mode 100644 index 3f425766..00000000 --- a/packages/@luke-ui/react/src/close-button/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { JSX } from 'react'; -import type { IconButtonProps } from '../icon-button/index.js'; -import { IconButton } from '../icon-button/index.js'; - -/** - * Props for `CloseButton`. - * - * @tier composed - */ -export interface CloseButtonProps extends Omit {} - -/** Icon button preset for close actions. */ -export function CloseButton(props: CloseButtonProps): JSX.Element { - const { tone = 'ghost', ...iconButtonProps } = props; - - return ; -}