@create-ui/cli 0.5.7 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-RMTTHCB3.js → chunk-2ELKDGGM.js} +3 -3
- package/dist/{chunk-RMTTHCB3.js.map → chunk-2ELKDGGM.js.map} +1 -1
- package/dist/chunk-643QI2I2.js +102 -0
- package/dist/chunk-643QI2I2.js.map +1 -0
- package/dist/{chunk-NQFMXHMH.js → chunk-KQTXDVKV.js} +3 -3
- package/dist/chunk-KQTXDVKV.js.map +1 -0
- package/dist/index.js +35 -35
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/skills/createui/SKILL.md +201 -177
- package/dist/skills/createui/agents/openai.yml +1 -1
- package/dist/skills/createui/cli.md +42 -42
- package/dist/skills/createui/customization.md +20 -15
- package/dist/skills/createui/evals/evals.json +68 -5
- package/dist/skills/createui/mcp.md +14 -5
- package/dist/skills/createui/reference/accordion.md +127 -0
- package/dist/skills/createui/reference/app-store-badge.md +88 -0
- package/dist/skills/createui/reference/aspect-ratio.md +52 -0
- package/dist/skills/createui/reference/avatar.md +230 -0
- package/dist/skills/createui/reference/badge.md +110 -0
- package/dist/skills/createui/reference/breadcrumb.md +153 -0
- package/dist/skills/createui/reference/button-group.md +116 -0
- package/dist/skills/createui/reference/button.md +104 -0
- package/dist/skills/createui/reference/checkbox-group.md +118 -0
- package/dist/skills/createui/reference/checkbox.md +79 -0
- package/dist/skills/createui/reference/chip.md +115 -0
- package/dist/skills/createui/reference/close-button.md +83 -0
- package/dist/skills/createui/reference/command.md +69 -0
- package/dist/skills/createui/reference/country-flag.md +109 -0
- package/dist/skills/createui/reference/credit-card-input.md +76 -0
- package/dist/skills/createui/reference/date-input.md +71 -0
- package/dist/skills/createui/reference/dropdown-menu.md +164 -0
- package/dist/skills/createui/reference/field.md +186 -0
- package/dist/skills/createui/reference/info-tooltip.md +110 -0
- package/dist/skills/createui/reference/inline-alert.md +146 -0
- package/dist/skills/createui/reference/input-group.md +171 -0
- package/dist/skills/createui/reference/input-otp.md +130 -0
- package/dist/skills/createui/reference/input-stepper.md +120 -0
- package/dist/skills/createui/reference/input.md +118 -0
- package/dist/skills/createui/reference/label.md +121 -0
- package/dist/skills/createui/reference/pagination.md +157 -0
- package/dist/skills/createui/reference/password-strength.md +70 -0
- package/dist/skills/createui/reference/phone-input.md +77 -0
- package/dist/skills/createui/reference/progress.md +158 -0
- package/dist/skills/createui/reference/radio-group.md +133 -0
- package/dist/skills/createui/reference/radio.md +79 -0
- package/dist/skills/createui/reference/scroll-area.md +212 -0
- package/dist/skills/createui/reference/segmented-control.md +146 -0
- package/dist/skills/createui/reference/select.md +204 -0
- package/dist/skills/createui/reference/separator.md +99 -0
- package/dist/skills/createui/reference/social-login-button.md +130 -0
- package/dist/skills/createui/reference/spinner.md +68 -0
- package/dist/skills/createui/reference/status-badge.md +89 -0
- package/dist/skills/createui/reference/switch-group.md +122 -0
- package/dist/skills/createui/reference/switch.md +75 -0
- package/dist/skills/createui/reference/tab-menu.md +165 -0
- package/dist/skills/createui/reference/text-link.md +84 -0
- package/dist/skills/createui/reference/textarea.md +50 -0
- package/dist/skills/createui/reference/toast.md +162 -0
- package/dist/skills/createui/reference/tooltip.md +63 -0
- package/dist/skills/createui/rules/composition.md +41 -25
- package/dist/skills/createui/rules/design.md +266 -0
- package/dist/skills/createui/rules/forms.md +44 -15
- package/dist/skills/createui/rules/icons.md +64 -18
- package/dist/skills/createui/rules/styling.md +53 -14
- package/dist/utils/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-M5DYT2NE.js +0 -64
- package/dist/chunk-M5DYT2NE.js.map +0 -1
- package/dist/chunk-NQFMXHMH.js.map +0 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!-- GENERATED FILE - do not edit. Source: registry/ui/text-link.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/text-link.md -->
|
|
2
|
+
|
|
3
|
+
# text-link
|
|
4
|
+
|
|
5
|
+
Inline anchor link with six color variants; underline, visited and disabled states, leadingIcon/trailingIcon, asChild
|
|
6
|
+
|
|
7
|
+
Install: `npx @create-ui/cli add text-link`
|
|
8
|
+
|
|
9
|
+
## Import
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { TextLink } from "@/components/ui/text-link"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Also exported: `textLinkVariants`
|
|
16
|
+
|
|
17
|
+
## TextLink props
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| variant | `primary \| neutral \| inverse \| danger \| success \| info` | `primary` |
|
|
22
|
+
| size | `xs \| sm \| md \| lg` | `md` |
|
|
23
|
+
| visited | `boolean` (Prop-controlled only (purple-500 text, violet underline); the component never reacts to the CSS :visited pseudo-class) | `false` |
|
|
24
|
+
| disabled | `boolean` (Sets aria-disabled, tabIndex=-1 and pointer-events-none but keeps href; remove href yourself if the target is meaningless) | `false` |
|
|
25
|
+
| asChild | `boolean` (The child's own children become the label; TextLink clones the child and re-injects the label wrapped in its content/underline/icon spans) | `false` |
|
|
26
|
+
| leadingIcon | `ReactNode` | - |
|
|
27
|
+
| trailingIcon | `ReactNode` | - |
|
|
28
|
+
| underline | `boolean` (Renders a hairline span (data-slot=text-link-underline), not text-decoration; base forces no-underline, so use this prop, never the Tailwind underline class) | `false` |
|
|
29
|
+
|
|
30
|
+
Extends `React.ComponentProps<"a">`.
|
|
31
|
+
|
|
32
|
+
## Icons
|
|
33
|
+
|
|
34
|
+
Icons go through icon props - never as children next to text, and never with sizing classes (the component sizes icons per `size`): `TextLink` (`leadingIcon` / `trailingIcon`). Import icons from `@create-ui/assets/icons` (Remix `Ri*`).
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
|
|
38
|
+
From `text-link-demo`:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { TextLink } from "@/components/ui/text-link"
|
|
42
|
+
|
|
43
|
+
export default function TextLinkDemo() {
|
|
44
|
+
return <TextLink href="#">Read the documentation</TextLink>
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
From `text-link-in-prose`:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { TextLink } from "@/components/ui/text-link"
|
|
52
|
+
|
|
53
|
+
export default function TextLinkInProse() {
|
|
54
|
+
return (
|
|
55
|
+
<p className="text-paragraph-md text-body max-w-prose">
|
|
56
|
+
Create UI ships components as source files you copy into your project.
|
|
57
|
+
Start with the{" "}
|
|
58
|
+
<TextLink href="#" underline>
|
|
59
|
+
installation guide
|
|
60
|
+
</TextLink>{" "}
|
|
61
|
+
to scaffold a new app, then browse the{" "}
|
|
62
|
+
<TextLink href="#">component reference</TextLink> to see every primitive
|
|
63
|
+
in one place.
|
|
64
|
+
</p>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
More: `npx @create-ui/cli view text-link` or MCP `get_item_examples_from_registries` with "text-link-demo" / "text-link-example".
|
|
70
|
+
|
|
71
|
+
## When to use
|
|
72
|
+
Inline link inside body copy, list items, table cells, and helper text, where the control must read as text in the line, not as a button. Renders a native `<a>` by default, or slots a router link (`next/link` etc.) via `asChild`. Do not use it when the control needs a filled hit area (use `Button`, optionally `asChild` over an `<a>`), for menu rows (`DropdownMenuItem`), or for breadcrumb trails (`BreadcrumbItem`).
|
|
73
|
+
|
|
74
|
+
## Gotchas
|
|
75
|
+
- There is no `as` prop: the root is always `<a>` unless `asChild` swaps in the child element. For a non-navigation click action, slot a `<button>` via `asChild`.
|
|
76
|
+
- `asChild` is smarter than a plain shadcn Slot: icons and `underline` still work because TextLink extracts the child's children as the label and clones the child with its own internal spans. The child must be a single valid element:
|
|
77
|
+
```tsx
|
|
78
|
+
<TextLink asChild underline trailingIcon={<RiArrowRightSLine />}>
|
|
79
|
+
<Link href="/docs">Continue</Link>
|
|
80
|
+
</TextLink>
|
|
81
|
+
```
|
|
82
|
+
- The label span is `whitespace-nowrap`: a multi-word link mid-sentence never wraps and can overflow narrow prose containers; keep labels short or override on `[data-slot=text-link-content]`.
|
|
83
|
+
- Focus is an `outline-*` treatment (transparent at rest, colored on focus-visible) per the system-wide outline rule; do not add `ring-*` via className.
|
|
84
|
+
- The label span carries small per-size horizontal padding (`px-0.5` to `px-1.5`), so the link text never sits perfectly flush with adjacent prose.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!-- GENERATED FILE - do not edit. Source: registry/ui/textarea.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/textarea.md -->
|
|
2
|
+
|
|
3
|
+
# textarea
|
|
4
|
+
|
|
5
|
+
Multi-line text input; size and state inherit from Field, resizable x/y/both with custom grip, loading spinner overlay
|
|
6
|
+
|
|
7
|
+
Install: `npx @create-ui/cli add textarea`
|
|
8
|
+
|
|
9
|
+
## Import
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { Textarea } from "@/components/ui/textarea"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Also exported: `textareaVariants`
|
|
16
|
+
|
|
17
|
+
## Textarea props
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| size | `xs \| sm \| md` | `sm` |
|
|
22
|
+
| resizable | `x \| y \| both` (Use this prop, never resize-x/resize-y classes; base is resize-none and the prop hides the webkit grip and overlays the custom corner glyph.) | - |
|
|
23
|
+
| loading | `boolean` (Makes the textarea readOnly + aria-busy with pointer-events-none, NOT disabled; it stays in the tab order and its value still submits.) | - |
|
|
24
|
+
|
|
25
|
+
Extends `React.ComponentProps<"textarea">`.
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
From `textarea-demo`:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { Textarea } from "@/components/ui/textarea"
|
|
33
|
+
|
|
34
|
+
export default function TextareaDemo() {
|
|
35
|
+
return <Textarea placeholder="Write something..." />
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
More: `npx @create-ui/cli view textarea` or MCP `get_item_examples_from_registries` with "textarea-demo" / "textarea-example".
|
|
40
|
+
|
|
41
|
+
## When to use
|
|
42
|
+
Multi-line free text: bios, messages, feedback, descriptions, comments. For single-line input use `Input`. Never place a raw `Textarea` inside an `InputGroup`; use `InputGroupTextarea` there, since the group manages size and state through its own context.
|
|
43
|
+
|
|
44
|
+
## Gotchas
|
|
45
|
+
- Field cascade: `size`, `loading`, and `disabled` resolve as explicit prop ?? Field context ?? default, so explicit props win. `invalid` is OR-combined instead: `<Field invalid>` alone flips the error styling, sets `aria-invalid`, and renders the warning icon, and `aria-invalid={false}` on the textarea cannot cancel it.
|
|
46
|
+
- The DOM shape changes with state: a `data-slot="textarea-wrapper"` div only exists when `resizable` is set or a status icon (loading/invalid) is showing; otherwise the bare `<textarea>` is returned. `className` always lands on the textarea itself, but parent-selector CSS can break across states.
|
|
47
|
+
- `loading` is not `disabled`: it sets `readOnly`, `aria-busy`, and `pointer-events-none`. If you need the field skipped on submit, set `disabled` too. When `loading` and invalid are both true, the spinner wins over the warning icon.
|
|
48
|
+
- Resize must go through the `resizable` prop. Adding `resize-y` via `className` shows the native browser grip with no design-system glyph; the prop hides the webkit resizer and grid-stacks the custom corner icon.
|
|
49
|
+
- The status icon (spinner / `RiErrorWarningLine`) renders bottom-LEFT inside the textarea and is `aria-hidden`; spinner size auto-pairs with the textarea size. Communicate the actual error text via `FieldHelper`, not the icon.
|
|
50
|
+
- Base height is `min-h-[132px]`, so small `rows` values have no visible effect; override height with a `min-h-*` class.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<!-- GENERATED FILE - do not edit. Source: registry/ui/toast.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/toast.md -->
|
|
2
|
+
|
|
3
|
+
# toast
|
|
4
|
+
|
|
5
|
+
Notification card with icon, action and close slots plus ToastProgress countdown; seven variants by four appearances
|
|
6
|
+
|
|
7
|
+
Install: `npx @create-ui/cli add toast`
|
|
8
|
+
|
|
9
|
+
## Import
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { Toast, ToastBody, ToastIcon, ToastContent, ToastTitle, ToastDescription, ToastAction, ToastClose, ToastProgress } from "@/components/ui/toast"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Also exported: `toastVariants`, `useToastContext`
|
|
16
|
+
|
|
17
|
+
## Toast props
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| variant | `primary \| neutral \| danger \| success \| warning \| info \| away` | `primary` |
|
|
22
|
+
| appearance | `solid \| soft \| outline \| default` (solid = filled, soft/outline/default = quieter surfaces) | `solid` |
|
|
23
|
+
|
|
24
|
+
Extends `React.ComponentProps<"div">`.
|
|
25
|
+
|
|
26
|
+
## ToastProgress props
|
|
27
|
+
|
|
28
|
+
| Prop | Type | Default |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| value | `number` | - |
|
|
31
|
+
| duration | `number` (CSS tween duration per value change (default 150ms); for a lifetime countdown set it to the toast's full lifetime in ms) | `150` |
|
|
32
|
+
|
|
33
|
+
Extends `React.ComponentProps<"div">`.
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
From `toast-demo`:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { RiInformationFill } from "@create-ui/assets/icons"
|
|
41
|
+
|
|
42
|
+
import {
|
|
43
|
+
Toast,
|
|
44
|
+
ToastBody,
|
|
45
|
+
ToastContent,
|
|
46
|
+
ToastDescription,
|
|
47
|
+
ToastIcon,
|
|
48
|
+
ToastTitle,
|
|
49
|
+
} from "@/components/ui/toast"
|
|
50
|
+
|
|
51
|
+
export default function ToastDemo() {
|
|
52
|
+
return (
|
|
53
|
+
<Toast>
|
|
54
|
+
<ToastBody>
|
|
55
|
+
<ToastIcon>
|
|
56
|
+
<RiInformationFill />
|
|
57
|
+
</ToastIcon>
|
|
58
|
+
<ToastContent>
|
|
59
|
+
<ToastTitle>New update available</ToastTitle>
|
|
60
|
+
<ToastDescription>Version 2.1 is ready to install.</ToastDescription>
|
|
61
|
+
</ToastContent>
|
|
62
|
+
</ToastBody>
|
|
63
|
+
</Toast>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
From `toast-with-progress`:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
"use client"
|
|
72
|
+
|
|
73
|
+
import * as React from "react"
|
|
74
|
+
import { RiInformationFill } from "@create-ui/assets/icons"
|
|
75
|
+
|
|
76
|
+
import { Button } from "@/components/ui/button"
|
|
77
|
+
import {
|
|
78
|
+
Toast,
|
|
79
|
+
ToastBody,
|
|
80
|
+
ToastContent,
|
|
81
|
+
ToastDescription,
|
|
82
|
+
ToastIcon,
|
|
83
|
+
ToastProgress,
|
|
84
|
+
ToastTitle,
|
|
85
|
+
} from "@/components/ui/toast"
|
|
86
|
+
|
|
87
|
+
const DURATION_MS = 5000
|
|
88
|
+
|
|
89
|
+
export default function ToastWithProgress() {
|
|
90
|
+
const [visible, setVisible] = React.useState(true)
|
|
91
|
+
const [progress, setProgress] = React.useState(0)
|
|
92
|
+
|
|
93
|
+
React.useEffect(() => {
|
|
94
|
+
if (!visible) return
|
|
95
|
+
|
|
96
|
+
const raf = requestAnimationFrame(() => setProgress(100))
|
|
97
|
+
const timeout = setTimeout(() => setVisible(false), DURATION_MS)
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
cancelAnimationFrame(raf)
|
|
101
|
+
clearTimeout(timeout)
|
|
102
|
+
}
|
|
103
|
+
}, [visible])
|
|
104
|
+
|
|
105
|
+
if (!visible) {
|
|
106
|
+
return (
|
|
107
|
+
<Button
|
|
108
|
+
variant="neutral-light"
|
|
109
|
+
appearance="outline"
|
|
110
|
+
onClick={() => {
|
|
111
|
+
setProgress(0)
|
|
112
|
+
setVisible(true)
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
Show toast again
|
|
116
|
+
</Button>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Toast variant="info" appearance="soft">
|
|
122
|
+
<ToastBody>
|
|
123
|
+
<ToastIcon>
|
|
124
|
+
<RiInformationFill />
|
|
125
|
+
</ToastIcon>
|
|
126
|
+
<ToastContent>
|
|
127
|
+
<ToastTitle>Auto-dismissing in 5s</ToastTitle>
|
|
128
|
+
<ToastDescription>
|
|
129
|
+
The progress bar tracks the remaining lifetime of this toast.
|
|
130
|
+
</ToastDescription>
|
|
131
|
+
</ToastContent>
|
|
132
|
+
</ToastBody>
|
|
133
|
+
<ToastProgress value={progress} duration={DURATION_MS} />
|
|
134
|
+
</Toast>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
More: `npx @create-ui/cli view toast` or MCP `get_item_examples_from_registries` with "toast-demo" / "toast-example".
|
|
140
|
+
|
|
141
|
+
## When to use
|
|
142
|
+
|
|
143
|
+
Ephemeral, floating confirmation of an action ("Draft saved", "Copied"); render it in a screen-level fixed container. Not for status that belongs to a section (use `InlineAlert`), blocking confirm/cancel decisions (use `Dialog`), or per-field validation (use `Field` helper/error text).
|
|
144
|
+
|
|
145
|
+
## Gotchas
|
|
146
|
+
|
|
147
|
+
- There is no `toast()` function, no `Toaster`, no app-level provider, no portal, no queue or positioning system. `Toast` is just the card; render active toasts from your own state into a fixed container. Do NOT install or import `sonner`.
|
|
148
|
+
- Dismiss lifecycle: `ToastClose` starts a 300ms fade/scale exit; the root's `onDismiss` fires only after the transition ends, then the component renders `null`. Remove the toast from your list in `onDismiss`, not in `ToastClose`'s `onClick`. Once dismissed it stays hidden; re-showing requires a remount (state toggle or new `key`).
|
|
149
|
+
- `ToastProgress` never ticks by itself and is `aria-hidden` (purely visual). For an auto-dismissing toast, set `duration` to the lifetime, flip `value` from 0 to 100 inside `requestAnimationFrame`, and pair it with a `setTimeout` that actually hides the toast:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
React.useEffect(() => {
|
|
153
|
+
const raf = requestAnimationFrame(() => setProgress(100))
|
|
154
|
+
const timeout = setTimeout(() => setVisible(false), DURATION_MS)
|
|
155
|
+
return () => { cancelAnimationFrame(raf); clearTimeout(timeout) }
|
|
156
|
+
}, [])
|
|
157
|
+
// <ToastProgress value={progress} duration={DURATION_MS} />
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- `ToastIcon`, `ToastDescription`, `ToastAction`, `ToastClose`, and `ToastProgress` read the Toast context, so they only style correctly inside `<Toast>`. Don't hand-set Button variants on `ToastAction`/`ToastClose` (the appearance-matched pairing is automatic) or color/size classes on the icon (the svg is auto-sized `size-5` and colored per variant/appearance).
|
|
161
|
+
- The root is hard-coded `w-[410px] min-w-[300px]`; override the width via `className` for narrow viewports.
|
|
162
|
+
- The root renders `role="status"` (polite live region); pass `role="alert"` for urgent messages. No Escape-key handling is wired; dismissal happens only through `ToastClose` (or `useToastContext().dismiss`).
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!-- GENERATED FILE - do not edit. Source: registry/ui/tooltip.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/tooltip.md -->
|
|
2
|
+
|
|
3
|
+
# tooltip
|
|
4
|
+
|
|
5
|
+
Radix hover tooltip for any trigger element; five color variants, optional arrow, side and offset control
|
|
6
|
+
|
|
7
|
+
Install: `npx @create-ui/cli add tooltip`
|
|
8
|
+
|
|
9
|
+
## Import
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Also exported: `tooltipContentVariants`
|
|
16
|
+
|
|
17
|
+
## TooltipContent props
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| children | `ReactNode` | - |
|
|
22
|
+
| className | `string` | - |
|
|
23
|
+
| variant | `primary \| neutral \| inverse \| danger \| info` | `primary` |
|
|
24
|
+
| showArrow | `boolean` (effective offset = sideOffset when true; sideOffset + 5 when false, so the chip keeps a visible gap without the caret) | `false` |
|
|
25
|
+
| side | `top \| bottom \| left \| right` (defaults to bottom; shadcn/Radix default to top, do not assume top) | `bottom` |
|
|
26
|
+
| sideOffset | `number` | `2.5` |
|
|
27
|
+
|
|
28
|
+
Extends `React.ComponentProps<typeof TooltipPrimitive.Content>`.
|
|
29
|
+
|
|
30
|
+
## Examples
|
|
31
|
+
|
|
32
|
+
From `tooltip-demo`:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
"use client"
|
|
36
|
+
|
|
37
|
+
import { Button } from "@/components/ui/button"
|
|
38
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
|
39
|
+
|
|
40
|
+
export default function TooltipDemo() {
|
|
41
|
+
return (
|
|
42
|
+
<Tooltip defaultOpen>
|
|
43
|
+
<TooltipTrigger asChild>
|
|
44
|
+
<Button appearance="outline" size="sm">
|
|
45
|
+
Hover me
|
|
46
|
+
</Button>
|
|
47
|
+
</TooltipTrigger>
|
|
48
|
+
<TooltipContent showArrow>Add to library</TooltipContent>
|
|
49
|
+
</Tooltip>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
More: `npx @create-ui/cli view tooltip` or MCP `get_item_examples_from_registries` with "tooltip-demo" / "tooltip-example".
|
|
55
|
+
|
|
56
|
+
## When to use
|
|
57
|
+
Tooltip is a short hover/focus hint for icon-only buttons, truncated labels, and similar read-only context. Content must stay brief: the chip caps at max-w-[200px] and wraps. For content with links, buttons, or inputs there is no popover component - put it inline; for a help icon next to a label use InfoTooltip; for post-action messages use Toast; for a persistent in-flow note use InlineAlert.
|
|
58
|
+
## Gotchas
|
|
59
|
+
- Tooltip self-provides its Radix Provider and passes delayDuration (default 0) explicitly to both Provider and Root, so an ancestor TooltipProvider's delayDuration never reaches it. Set delay per instance: `<Tooltip delayDuration={400}>`. The exported TooltipProvider only matters for raw TooltipPrimitive.Root subtrees.
|
|
60
|
+
- align is hardcoded to "center" and Omit-ed from the passthrough type; it cannot be overridden.
|
|
61
|
+
- TooltipTrigger should use asChild wrapping a focusable registry element (Button, link); the trigger always gets an `inline-flex` class so the wrapped child lays out consistently.
|
|
62
|
+
- TooltipArrow is internal and not exported; the only way to render the caret is `showArrow` on TooltipContent, and its fill auto-matches the chosen variant. Never try to style or compose the arrow separately.
|
|
63
|
+
- InfoTooltip is a separate ready-made component for inline "what is this" affordances; do not hand-compose Tooltip around an info icon.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Component Composition
|
|
2
2
|
|
|
3
|
-
How Create UI components fit together. Compose primitives
|
|
3
|
+
How Create UI components fit together. Compose primitives - never reroll a `<select>`, a custom callout, or a hand-styled loading button when a component already exists. Every component name below is in the `@createui` registry; add any of them with `npx @create-ui/cli add <name>`.
|
|
4
4
|
|
|
5
5
|
## Contents
|
|
6
6
|
|
|
@@ -8,16 +8,16 @@ How Create UI components fit together. Compose primitives — never reroll a `<s
|
|
|
8
8
|
- Callouts use InlineAlert
|
|
9
9
|
- Toasts use the Toast component
|
|
10
10
|
- Choosing between overlay components
|
|
11
|
-
- Button has a `loading` prop
|
|
11
|
+
- Button has a `loading` prop - never hand-build a spinner button
|
|
12
12
|
- Tabbed navigation uses TabMenu
|
|
13
|
-
- Avatar
|
|
13
|
+
- Avatar composition (AvatarText, not AvatarFallback)
|
|
14
14
|
- Use existing components instead of custom markup
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
## Items always inside their Group component
|
|
19
19
|
|
|
20
|
-
Never render menu/list items directly inside the content container
|
|
20
|
+
Never render menu/list items directly inside the content container - always wrap them in the matching `*Group`.
|
|
21
21
|
|
|
22
22
|
**Incorrect:**
|
|
23
23
|
|
|
@@ -42,17 +42,17 @@ Never render menu/list items directly inside the content container — always wr
|
|
|
42
42
|
|
|
43
43
|
This applies to every group-based component:
|
|
44
44
|
|
|
45
|
-
| Item | Group |
|
|
46
|
-
|
|
47
|
-
| `SelectItem`, `SelectLabel` | `SelectGroup` |
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
45
|
+
| Item | Group | Requirement |
|
|
46
|
+
|------|-------|-------------|
|
|
47
|
+
| `SelectItem`, `SelectLabel` | `SelectGroup` | Structural - always wrap |
|
|
48
|
+
| `CommandItem` | `CommandGroup` | Structural - always wrap |
|
|
49
|
+
| `DropdownMenuItem`, `DropdownMenuLabel` | `DropdownMenuGroup` | Semantic - group related items (with `DropdownMenuLabel` / `DropdownMenuSeparator`); a lone item may sit directly in `DropdownMenuContent` |
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
53
53
|
## Callouts use InlineAlert
|
|
54
54
|
|
|
55
|
-
Use `InlineAlert` for callouts. Don't hand-roll a styled `<div>`
|
|
55
|
+
Use `InlineAlert` for callouts. Don't hand-roll a styled `<div>` - and don't look for a shadcn-style generic alert (or a page-banner) component; `InlineAlert` is the callout primitive.
|
|
56
56
|
|
|
57
57
|
**Incorrect:**
|
|
58
58
|
|
|
@@ -94,13 +94,13 @@ import {
|
|
|
94
94
|
</InlineAlert>
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
`InlineAlert` takes `variant` (`primary` | `neutral` | `danger` | `success` | `warning` | `info` | `away`) and `appearance` (`default` | `solid` | `soft` | `outline`). For a dismissible callout, add `<InlineAlertClose />` as a direct child and handle `onDismiss` on the root. For a full-width page banner, place an `InlineAlert` in a full-width container
|
|
97
|
+
`InlineAlert` takes `variant` (`primary` | `neutral` | `danger` | `success` | `warning` | `info` | `away`) and `appearance` (`default` | `solid` | `soft` | `outline`). For a dismissible callout, add `<InlineAlertClose />` as a direct child and handle `onDismiss` on the root. For a full-width page banner, place an `InlineAlert` in a full-width container - there is no separate banner component.
|
|
98
98
|
|
|
99
99
|
---
|
|
100
100
|
|
|
101
101
|
## Toasts use the Toast component
|
|
102
102
|
|
|
103
|
-
Toasts are the registry's own `toast` component
|
|
103
|
+
Toasts are the registry's own `toast` component - **not `sonner`**. There is no `toast()` function to import; compose the `Toast` parts and render it from your notification state.
|
|
104
104
|
|
|
105
105
|
**Incorrect:**
|
|
106
106
|
|
|
@@ -140,7 +140,7 @@ import {
|
|
|
140
140
|
|
|
141
141
|
`Toast` takes `variant` (`primary` | `neutral` | `danger` | `success` | `warning` | `info` | `away`) and `appearance` (`solid` | `soft` | `outline` | `default`), plus an `onDismiss` callback. Add `<ToastClose />` for an explicit close affordance and `<ToastProgress />` for an auto-dismiss countdown bar.
|
|
142
142
|
|
|
143
|
-
There is no provider, queue, or stacking system
|
|
143
|
+
There is no provider, queue, or stacking system - you own the notification state and the placement. Render active toasts from your state into a fixed container:
|
|
144
144
|
|
|
145
145
|
```tsx
|
|
146
146
|
const [toasts, setToasts] = React.useState<AppToast[]>([])
|
|
@@ -168,7 +168,7 @@ const [toasts, setToasts] = React.useState<AppToast[]>([])
|
|
|
168
168
|
|
|
169
169
|
## Choosing between overlay components
|
|
170
170
|
|
|
171
|
-
Pick the overlay that matches the interaction
|
|
171
|
+
Pick the overlay that matches the interaction - these are the overlays that exist.
|
|
172
172
|
|
|
173
173
|
| Use case | Component |
|
|
174
174
|
|----------|-----------|
|
|
@@ -177,13 +177,13 @@ Pick the overlay that matches the interaction — these are the overlays that ex
|
|
|
177
177
|
| Action menu on a trigger | `DropdownMenu` |
|
|
178
178
|
| Command palette / quick switcher | `Command` (inline) / `CommandDialog` (modal) |
|
|
179
179
|
|
|
180
|
-
There is **no dialog, popover, sheet, drawer, alert-dialog, or hover-card component**. The only modal surface is `CommandDialog` (shipped with `command`). For other modal or contextual-panel needs, don't invent a lookalike from raw markup
|
|
180
|
+
There is **no dialog, popover, sheet, drawer, alert-dialog, or hover-card component**. The only modal surface is `CommandDialog` (shipped with `command`). For other modal or contextual-panel needs, don't invent a lookalike from raw markup - surface the flow inline (e.g. an expanding section, a dedicated route, or an `InlineAlert` confirmation) or ask the user before hand-rolling an overlay.
|
|
181
181
|
|
|
182
182
|
---
|
|
183
183
|
|
|
184
|
-
## Button has a `loading` prop
|
|
184
|
+
## Button has a `loading` prop - never hand-build a spinner button
|
|
185
185
|
|
|
186
|
-
`Button` ships a real `loading` prop. It renders the `Spinner` and disables interaction for you
|
|
186
|
+
`Button` ships a real `loading` prop. It renders the `Spinner` and disables interaction for you - do not compose a `Spinner` + `disabled` button by hand.
|
|
187
187
|
|
|
188
188
|
**Incorrect:**
|
|
189
189
|
|
|
@@ -200,7 +200,7 @@ There is **no dialog, popover, sheet, drawer, alert-dialog, or hover-card compon
|
|
|
200
200
|
<Button loading>Saving…</Button>
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
-
For icons, use the `leadingIcon` / `trailingIcon` props (or `iconOnly` for an icon-only button)
|
|
203
|
+
For icons, use the `leadingIcon` / `trailingIcon` props (or `iconOnly` for an icon-only button) - never wrap raw `<svg>` children or add sizing classes; the component sizes the icon per `size`.
|
|
204
204
|
|
|
205
205
|
```tsx
|
|
206
206
|
import { RiSearchLine } from "@create-ui/assets/icons"
|
|
@@ -209,13 +209,13 @@ import { RiSearchLine } from "@create-ui/assets/icons"
|
|
|
209
209
|
<Button iconOnly aria-label="Search" leadingIcon={<RiSearchLine />} />
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
-
Remember the Button API: `variant` is `primary | neutral-solid | neutral-light | danger | success | inverse-solid | inverse-light`, and the outlined/ghost looks come from `appearance` (`solid | outline | ghost | soft`). There is no outline or destructive `variant` value
|
|
212
|
+
Remember the Button API: `variant` is `primary | neutral-solid | neutral-light | danger | success | inverse-solid | inverse-light`, and the outlined/ghost looks come from `appearance` (`solid | outline | ghost | soft`). There is no outline or destructive `variant` value - use `appearance="outline"` for the outlined look and `variant="danger"` for destructive actions. See `rules/styling.md` for the full variant/appearance reference.
|
|
213
213
|
|
|
214
214
|
---
|
|
215
215
|
|
|
216
216
|
## Tabbed navigation uses TabMenu
|
|
217
217
|
|
|
218
|
-
Tabs are the `tab-menu` component
|
|
218
|
+
Tabs are the `tab-menu` component - there is no shadcn-style tabs / tabs-list / tabs-trigger set. `TabMenu` wraps `TabMenuItem`s and owns the selection (`defaultValue`, or controlled `value` / `onValueChange`). It renders the menu only - render the active panel yourself from the value; there is no content component.
|
|
219
219
|
|
|
220
220
|
```tsx
|
|
221
221
|
import { TabMenu, TabMenuItem } from "@/components/ui/tab-menu"
|
|
@@ -234,27 +234,43 @@ const [tab, setTab] = React.useState("overview")
|
|
|
234
234
|
|
|
235
235
|
---
|
|
236
236
|
|
|
237
|
-
## Avatar
|
|
237
|
+
## Avatar composition (AvatarText, not AvatarFallback)
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
**`AvatarFallback` does not exist in Create UI** - the fallback/initials slot is `AvatarText`. It renders only while the sibling `AvatarImage` has not loaded (or when there is no image), so an image avatar should always carry one:
|
|
240
240
|
|
|
241
241
|
```tsx
|
|
242
|
+
import { Avatar, AvatarImage, AvatarText } from "@/components/ui/avatar"
|
|
243
|
+
|
|
244
|
+
<Avatar>
|
|
245
|
+
<AvatarImage src="/avatar.png" alt="User" />
|
|
246
|
+
<AvatarText>JD</AvatarText>
|
|
247
|
+
</Avatar>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Initials-only avatars pick a real color `variant`; presence comes from `AvatarBadge` + `AvatarBadgeStatus`; stacks are `AvatarGroup` (+ `AvatarGroupAction` for the "+5" affordance):
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
<Avatar variant="weak-blue"><AvatarText>YT</AvatarText></Avatar>
|
|
254
|
+
|
|
242
255
|
<Avatar>
|
|
243
256
|
<AvatarImage src="/avatar.png" alt="User" />
|
|
244
|
-
<
|
|
257
|
+
<AvatarText>JD</AvatarText>
|
|
258
|
+
<AvatarBadge><AvatarBadgeStatus variant="online" /></AvatarBadge>
|
|
245
259
|
</Avatar>
|
|
246
260
|
```
|
|
247
261
|
|
|
262
|
+
See `reference/avatar.md` for the full prop tables (`size` `2xs`–`2xl`, `shape`, 55 color variants, `AvatarIcon`, `AvatarRing`).
|
|
263
|
+
|
|
248
264
|
---
|
|
249
265
|
|
|
250
266
|
## Use existing components instead of custom markup
|
|
251
267
|
|
|
252
|
-
If a primitive already covers the job, use it
|
|
268
|
+
If a primitive already covers the job, use it - don't reach for raw elements or utility-class fakes.
|
|
253
269
|
|
|
254
270
|
| Instead of | Use |
|
|
255
271
|
|---|---|
|
|
256
272
|
| `<hr>` or `<div className="border-t">` | `<Separator />` |
|
|
257
273
|
| `<span className="rounded-full bg-green-100 …">` | `<Badge variant="success">Active</Badge>` |
|
|
258
|
-
| A status dot built from a styled `<span>` | `<StatusBadge variant="success" />` (it renders the dot only
|
|
274
|
+
| A status dot built from a styled `<span>` | `<StatusBadge variant="success" />` (it renders the dot only - put the label next to it). `variant`: `primary`, `danger`, `success`, `warning`, `info`, `highlighted`, `away`, `verified`, `cyan`, `lime`, `neutral`, `white` - note `danger`, not `error` |
|
|
259
275
|
| A removable tag built from `<span>` + `<button>` | `<Chip onClose={…}>…</Chip>` |
|
|
260
276
|
| A hand-rolled `animate-spin` loading indicator | `<Spinner />` |
|