@florianke/components 0.0.3 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +466 -30
  2. package/dist/alert-dialog.d.mts +2 -31
  3. package/dist/alert-dialog.d.mts.map +1 -1
  4. package/dist/alert-dialog.mjs +2 -46
  5. package/dist/alert-dialog.mjs.map +1 -1
  6. package/dist/avatar.mjs +1 -1
  7. package/dist/button.mjs +1 -1
  8. package/dist/card.mjs +1 -1
  9. package/dist/components/alert-dialog/alert-dialog.d.mts +35 -0
  10. package/dist/components/alert-dialog/alert-dialog.d.mts.map +1 -0
  11. package/dist/components/alert-dialog/alert-dialog.mjs +50 -0
  12. package/dist/components/alert-dialog/alert-dialog.mjs.map +1 -0
  13. package/dist/components/select/select.d.mts +42 -0
  14. package/dist/components/select/select.d.mts.map +1 -0
  15. package/dist/components/select/select.mjs +102 -0
  16. package/dist/components/select/select.mjs.map +1 -0
  17. package/dist/components/toast/toast.d.mts +45 -0
  18. package/dist/components/toast/toast.d.mts.map +1 -0
  19. package/dist/components/toast/toast.mjs +156 -0
  20. package/dist/components/toast/toast.mjs.map +1 -0
  21. package/dist/components/tooltip/tooltip.d.mts +30 -0
  22. package/dist/components/tooltip/tooltip.d.mts.map +1 -0
  23. package/dist/components/tooltip/tooltip.mjs +40 -0
  24. package/dist/components/tooltip/tooltip.mjs.map +1 -0
  25. package/dist/dropdown-menu.mjs +1 -1
  26. package/dist/field.mjs +1 -1
  27. package/dist/heading.mjs +1 -1
  28. package/dist/hint.mjs +1 -1
  29. package/dist/hooks/use-confirm.mjs.map +1 -1
  30. package/dist/input.mjs +1 -1
  31. package/dist/label.mjs +1 -1
  32. package/dist/{cn-BpvCVwZv.mjs → lib/cn.mjs} +2 -2
  33. package/dist/lib/cn.mjs.map +1 -0
  34. package/dist/providers.mjs.map +1 -1
  35. package/dist/select.d.mts +2 -38
  36. package/dist/select.d.mts.map +1 -1
  37. package/dist/select.mjs +2 -98
  38. package/dist/select.mjs.map +1 -1
  39. package/dist/text.mjs +1 -1
  40. package/dist/toast.d.mts +4 -42
  41. package/dist/toast.d.mts.map +1 -1
  42. package/dist/toast.mjs +3 -152
  43. package/dist/toast.mjs.map +1 -1
  44. package/dist/tooltip.d.mts +2 -26
  45. package/dist/tooltip.d.mts.map +1 -1
  46. package/dist/tooltip.mjs +2 -36
  47. package/dist/tooltip.mjs.map +1 -1
  48. package/package.json +41 -1
  49. package/dist/cn-BpvCVwZv.mjs.map +0 -1
package/README.md CHANGED
@@ -5,7 +5,7 @@ A small, opinionated React component library built on top of [Base UI](https://b
5
5
  - **Headless-first** — behavior, accessibility, and keyboard interaction are handled by Base UI; this library only adds opinionated styling and a few ergonomic defaults.
6
6
  - **Themeable** — all colors are CSS variables (`--background`, `--foreground`, `--ring`, ...), so you can restyle the whole library by overriding a handful of custom properties.
7
7
  - **Tree-shakeable** — every component ships as its own entry point, so bundlers only include what you actually import.
8
- - **Client-ready** — every interactive component is already marked with `"use client"`, so it works out of the box inside a Next.js App Router Server Component tree.
8
+ - **Client-ready** — every interactive component is already marked with `"use client"`, so it works out of the box inside a Next.js App Router Server Component tree — including dot-notation access like `<Select.Trigger>` directly from a Server Component page (no `"use client"` needed in your own file just to reference it).
9
9
 
10
10
  ## Table of Contents
11
11
 
@@ -14,14 +14,24 @@ A small, opinionated React component library built on top of [Base UI](https://b
14
14
  - [Setup](#setup)
15
15
  - [Quick Start](#quick-start)
16
16
  - [Theming](#theming)
17
+ - [Providers](#providers)
17
18
  - [Components](#components)
19
+ - [AlertDialog](#alertdialog)
18
20
  - [Avatar](#avatar)
19
21
  - [Button](#button)
20
22
  - [Card](#card)
21
23
  - [DropdownMenu](#dropdownmenu)
24
+ - [Field](#field)
25
+ - [Heading](#heading)
26
+ - [Hint](#hint)
22
27
  - [Input](#input)
28
+ - [Label](#label)
23
29
  - [Select](#select)
30
+ - [Text](#text)
31
+ - [Toast](#toast)
32
+ - [Tooltip](#tooltip)
24
33
  - [Hooks](#hooks)
34
+ - [useConfirm](#useconfirm)
25
35
  - [useInitials](#useinitials)
26
36
  - [Package Exports](#package-exports)
27
37
  - [License](#license)
@@ -81,11 +91,27 @@ Dark mode is automatic via `prefers-color-scheme`, and can be forced by toggling
81
91
  <html className="dark">
82
92
  ```
83
93
 
84
- ## Quick Start
94
+ ### 4. Wrap your app in `Providers`
95
+
96
+ `Toast`, `Tooltip`, and `useConfirm` all need a bit of context set up somewhere above where you use them. Wrap your root layout once with [`Providers`](#providers) and you're done.
85
97
 
86
98
  ```tsx
87
- "use client";
99
+ import { Providers } from "@florianke/components";
100
+
101
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
102
+ return (
103
+ <html>
104
+ <body>
105
+ <Providers>{children}</Providers>
106
+ </body>
107
+ </html>
108
+ );
109
+ }
110
+ ```
111
+
112
+ ## Quick Start
88
113
 
114
+ ```tsx
89
115
  import { Avatar, Button, Card, Input } from "@florianke/components";
90
116
 
91
117
  export default function Example() {
@@ -108,17 +134,21 @@ export default function Example() {
108
134
 
109
135
  Every component reads its colors from CSS custom properties, defined once in `styles.css` and re-defined inside `.dark`:
110
136
 
111
- | Token | Used for |
112
- | ----------------------- | ------------------------------------------------------ |
113
- | `--background` | Page background |
114
- | `--foreground` | Primary text |
115
- | `--card` / `--card-foreground` | `Card` surface and its text |
116
- | `--popover` / `--popover-foreground` | Floating surfaces (`DropdownMenu`, `Select` popups) |
117
- | `--border` | Hairline borders and dividers |
118
- | `--input` / `--input-border` | Form control backgrounds and borders |
119
- | `--muted` / `--muted-foreground` | Recessed surfaces and secondary text |
120
- | `--accent` / `--accent-foreground` | Hover/highlighted state on menu and select items |
121
- | `--ring` | Focus rings |
137
+ | Token | Used for |
138
+ | -------------------------------------- | ---------------------------------------------------------------------------------- |
139
+ | `--background` | Page background |
140
+ | `--foreground` | Primary text |
141
+ | `--card` / `--card-foreground` | `Card` surface and its text |
142
+ | `--popover` / `--popover-foreground` | Floating surfaces (`DropdownMenu`, `Select`, `Toast`, `Tooltip`, `AlertDialog`) |
143
+ | `--border` | Hairline borders and dividers |
144
+ | `--input` / `--input-border` | Form control backgrounds and borders |
145
+ | `--muted` / `--muted-foreground` | Recessed surfaces and secondary text |
146
+ | `--accent` / `--accent-foreground` | Hover/highlighted state on menu and select items |
147
+ | `--ring` | Focus rings |
148
+ | `--success` | Success state (`Toast` type `"success"`) |
149
+ | `--warning` | Warning state (`Toast` type `"warning"`) |
150
+ | `--destructive` | Error/destructive state (`Toast` type `"error"`, `Button` variant `"destructive"`, invalid `Input`, `Hint` variant `"error"`) |
151
+ | `--info` | Info state (`Toast` type `"info"`) |
122
152
 
123
153
  Override any of these in your own CSS (after importing the package's stylesheet) to reskin the whole library:
124
154
 
@@ -128,8 +158,89 @@ Override any of these in your own CSS (after importing the package's stylesheet)
128
158
  }
129
159
  ```
130
160
 
161
+ ## Providers
162
+
163
+ A single wrapper component that bundles everything the library needs context for: toast notifications, tooltip delay grouping, and the [`useConfirm`](#useconfirm) dialog. Add it once, near the root of your app, and every `Toast`, `Tooltip`, and `useConfirm()` call anywhere below it will just work — no further setup required.
164
+
165
+ ```tsx
166
+ import { Providers } from "@florianke/components";
167
+
168
+ <Providers>{children}</Providers>
169
+ ```
170
+
171
+ Internally, it's equivalent to:
172
+
173
+ ```tsx
174
+ <Toast.Provider>
175
+ <Tooltip.Provider>
176
+ <ConfirmProvider>
177
+ {children}
178
+ <Toast.Viewport>
179
+ <Toast.List />
180
+ </Toast.Viewport>
181
+ </ConfirmProvider>
182
+ </Tooltip.Provider>
183
+ </Toast.Provider>
184
+ ```
185
+
186
+ If you need to configure something the bundled version doesn't expose (e.g. a custom tooltip delay), compose the pieces yourself instead of using `Providers` — see [Toast](#toast), [Tooltip](#tooltip), and [useConfirm](#useconfirm).
187
+
188
+ | Prop | Type | Default | Description |
189
+ | ---------- | ------------------- | ------- | -------------- |
190
+ | `children` | `React.ReactNode` | — | Your app. |
191
+
131
192
  ## Components
132
193
 
194
+ ### AlertDialog
195
+
196
+ A modal confirmation dialog built on Base UI's `AlertDialog`.
197
+
198
+ ```tsx
199
+ import { AlertDialog, Button } from "@florianke/components";
200
+
201
+ <AlertDialog>
202
+ <AlertDialog.Trigger render={<Button variant="destructive" size="sm" />}>
203
+ Discard draft
204
+ </AlertDialog.Trigger>
205
+ <AlertDialog.Content>
206
+ <div className="flex flex-col gap-1">
207
+ <AlertDialog.Title>Discard draft?</AlertDialog.Title>
208
+ <AlertDialog.Description>
209
+ You can&rsquo;t undo this action.
210
+ </AlertDialog.Description>
211
+ </div>
212
+ <div className="flex justify-end gap-2">
213
+ <AlertDialog.Close>Cancel</AlertDialog.Close>
214
+ <AlertDialog.Close render={<Button variant="destructive" />}>
215
+ Discard
216
+ </AlertDialog.Close>
217
+ </div>
218
+ </AlertDialog.Content>
219
+ </AlertDialog>
220
+ ```
221
+
222
+ For the common "confirm before doing something destructive" flow, prefer the imperative [`useConfirm`](#useconfirm) hook instead of building the dialog by hand — it already includes this exact structure, plus an optional type-to-confirm verification step.
223
+
224
+ #### `AlertDialog` (root)
225
+
226
+ Forwards all props to Base UI's `AlertDialog.Root` — e.g. `open`, `defaultOpen`, `onOpenChange`.
227
+
228
+ #### `AlertDialog.Trigger`
229
+
230
+ Forwards all props to Base UI's `AlertDialog.Trigger` — most commonly `render` (to swap the rendered element/component, e.g. our own `Button`).
231
+
232
+ #### `AlertDialog.Content`
233
+
234
+ Renders the backdrop, positions the popup at the center of the screen, and handles the open/close transition. Accepts standard `<div>` props plus everything Base UI's `AlertDialog.Popup` accepts.
235
+
236
+ #### `AlertDialog.Title` / `AlertDialog.Description`
237
+
238
+ Forward all props to Base UI's `AlertDialog.Title` / `AlertDialog.Description` (which wire up the `aria-labelledby`/`aria-describedby` linking automatically).
239
+
240
+ #### `AlertDialog.Close`
241
+
242
+ Forwards all props to Base UI's `AlertDialog.Close`. Defaults to rendering as our own `Button` with `variant="outline"` — pass `render={<Button variant="destructive" />}` for the confirming action.
243
+
133
244
  ### Avatar
134
245
 
135
246
  Renders an image, and automatically falls back to the person's initials — shown immediately, with the image swapping in as soon as it finishes loading (no flash, no artificial delay).
@@ -143,8 +254,8 @@ import { Avatar } from "@florianke/components";
143
254
  ```
144
255
 
145
256
  | Prop | Type | Default | Description |
146
- | ----------- | --------------------------------- | ----------- | ---------------------------------------------------------------------------- |
147
- | `name` | `string` | —(required | Full name. Used for the `alt` text and to derive the fallback initials via [`useInitials`](#useinitials). |
257
+ | ----------- | ---------------------------------- | ----------- | ---------------------------------------------------------------------------- |
258
+ | `name` | `string` | — (required) | Full name. Used for the `alt` text and to derive the fallback initials via [`useInitials`](#useinitials). |
148
259
  | `src` | `string` | `undefined` | Image URL. When omitted, the initials fallback is shown. |
149
260
  | `variant` | `"rounded" \| "squared"` | `"rounded"` | Shape of the avatar. |
150
261
  | `size` | `"small" \| "base" \| "large"` | `"base"` | Size of the avatar. |
@@ -162,6 +273,7 @@ import { Button } from "@florianke/components";
162
273
  <Button variant="outline">Outline</Button>
163
274
  <Button variant="ghost">Ghost</Button>
164
275
  <Button variant="link">Link</Button>
276
+ <Button variant="destructive">Delete</Button>
165
277
 
166
278
  <Button size="sm">Small</Button>
167
279
  <Button size="md">Medium</Button>
@@ -175,12 +287,12 @@ import { Button } from "@florianke/components";
175
287
  </Button>
176
288
  ```
177
289
 
178
- | Prop | Type | Default | Description |
179
- | ----------- | -------------------------------------------------------------------- | ----------- | ---------------------------------------------------------- |
180
- | `variant` | `"primary" \| "secondary" \| "outline" \| "ghost" \| "link"` | `"primary"` | Visual style. |
181
- | `size` | `"sm" \| "md"` | `"md"` | Height/padding/type scale. Ignored when `variant="link"`. |
182
- | `disabled` | `boolean` | `false` | Disables the button (via the native `disabled` attribute). |
183
- | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
290
+ | Prop | Type | Default | Description |
291
+ | ----------- | --------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------- |
292
+ | `variant` | `"primary" \| "secondary" \| "outline" \| "ghost" \| "link" \| "destructive"` | `"primary"` | Visual style. Use `"destructive"` for irreversible actions (delete, discard). |
293
+ | `size` | `"sm" \| "md"` | `"md"` | Height/padding/type scale. Ignored when `variant="link"`. |
294
+ | `disabled` | `boolean` | `false` | Disables the button (via the native `disabled` attribute). |
295
+ | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
184
296
 
185
297
  Also accepts every other native `<button>` prop (`onClick`, `type`, `ref`, ...), since it's built on Base UI's `Button`.
186
298
 
@@ -246,6 +358,70 @@ Forwards all props to Base UI's `Menu.Trigger` — most commonly `render` (to sw
246
358
 
247
359
  Forwards all props to Base UI's `Menu.Item` — most commonly `disabled` and `onClick`.
248
360
 
361
+ ### Field
362
+
363
+ A layout primitive for building form fields: just a `<div>` with `flex flex-col gap-y-1.5`. Compose it with [`Label`](#label), [`Input`](#input), and [`Hint`](#hint).
364
+
365
+ ```tsx
366
+ import { Field, Hint, Input, Label } from "@florianke/components";
367
+
368
+ <Field className="w-64">
369
+ <Label htmlFor="email">Email</Label>
370
+ <Input id="email" placeholder="you@example.com" />
371
+ <Hint>We&rsquo;ll never share your email.</Hint>
372
+ </Field>
373
+
374
+ {/* Error state */}
375
+ <Field className="w-64">
376
+ <Label htmlFor="email">Email</Label>
377
+ <Input id="email" aria-invalid="true" defaultValue="not-an-email" />
378
+ <Hint variant="error">Please enter a valid email address.</Hint>
379
+ </Field>
380
+ ```
381
+
382
+ | Prop | Type | Default | Description |
383
+ | ----------- | ----------- | ----------- | --------------------------------------------- |
384
+ | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
385
+
386
+ Accepts every other standard `<div>` prop.
387
+
388
+ ### Heading
389
+
390
+ A page/section heading with three preset sizes. Renders the actual matching HTML tag (`<h1>`, `<h2>`, or `<h3>`) for correct document outline/accessibility.
391
+
392
+ ```tsx
393
+ import { Heading } from "@florianke/components";
394
+
395
+ <Heading level="h1">Page title</Heading>
396
+ <Heading level="h2">Section title</Heading>
397
+ <Heading level="h3">Subsection title</Heading>
398
+ ```
399
+
400
+ | Prop | Type | Default | Description |
401
+ | ----------- | ------------------------- | ------- | --------------------------------------------- |
402
+ | `level` | `"h1" \| "h2" \| "h3"` | `"h1"` | Determines both the rendered tag and the type scale. |
403
+ | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
404
+
405
+ Accepts every other standard heading (`<h1>`/`<h2>`/`<h3>`) prop.
406
+
407
+ ### Hint
408
+
409
+ Small helper/status text for form fields — e.g. under an [`Input`](#input) inside a [`Field`](#field). The `"error"` variant only changes the text color to `--destructive`; it doesn't add an icon or change the layout.
410
+
411
+ ```tsx
412
+ import { Hint } from "@florianke/components";
413
+
414
+ <Hint>Must be at least 8 characters long.</Hint>
415
+ <Hint variant="error">Password must be at least 8 characters long.</Hint>
416
+ ```
417
+
418
+ | Prop | Type | Default | Description |
419
+ | ----------- | ----------------------- | -------- | --------------------------------------------- |
420
+ | `variant` | `"info" \| "error"` | `"info"` | `"error"` renders the text in `--destructive`. |
421
+ | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
422
+
423
+ Accepts every other standard `<span>` prop.
424
+
249
425
  ### Input
250
426
 
251
427
  ```tsx
@@ -255,16 +431,47 @@ import { Input } from "@florianke/components";
255
431
  <Input size="sm" placeholder="Small" />
256
432
  <Input disabled placeholder="Can't edit this" />
257
433
  <Input defaultValue="1920" />
434
+
435
+ {/* Invalid state — red border + ring, works with any input */}
436
+ <Input aria-invalid="true" defaultValue="not-an-email" />
437
+
438
+ {/* Password: renders a show/hide toggle button automatically */}
439
+ <Input type="password" defaultValue="hunter2" />
440
+ <Input type="password" aria-invalid="true" defaultValue="short" />
258
441
  ```
259
442
 
260
- | Prop | Type | Default | Description |
261
- | ----------- | ------------------- | ------- | --------------------------------------------- |
262
- | `size` | `"sm" \| "md"` | `"md"` | Height/padding/type scale. |
263
- | `disabled` | `boolean` | `false` | Disables the input. |
264
- | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
443
+ | Prop | Type | Default | Description |
444
+ | ------------- | ------------------- | ------- | --------------------------------------------- |
445
+ | `size` | `"sm" \| "md"` | `"md"` | Height/padding/type scale. |
446
+ | `disabled` | `boolean` | `false` | Disables the input. |
447
+ | `type` | `string` | `"text"` | Standard HTML input type. `"password"` gets special treatment (see below). |
448
+ | `aria-invalid` | `boolean \| "true" \| "false"` | `undefined` | When truthy, the border and focus ring turn `--destructive` red. |
449
+ | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
265
450
 
266
451
  Also accepts every other native `<input>` prop (`value`, `onChange`, `placeholder`, `ref`, ...), except `size` which is overridden by the prop above.
267
452
 
453
+ When `type="password"`, `Input` renders a bordered group with the text field on the left and a show/hide toggle button (eye icon) on the right, instead of a plain `<input type="password">`. All other props (`aria-invalid`, `disabled`, `value`, `onChange`, ...) still apply to the underlying field as usual.
454
+
455
+ ### Label
456
+
457
+ A form field label. Built on top of [`Text`](#text) (`as="label"`), defaulting to a slightly bolder weight than body text.
458
+
459
+ ```tsx
460
+ import { Label } from "@florianke/components";
461
+
462
+ <Label htmlFor="name">Name</Label>
463
+ <Label size="small" htmlFor="name">Name</Label>
464
+ <Label weight="regular" htmlFor="name">Name</Label>
465
+ ```
466
+
467
+ | Prop | Type | Default | Description |
468
+ | ----------- | ------------------------------- | -------- | ---------------------------- |
469
+ | `size` | `"small" \| "base" \| "large"` | `"base"` | Type scale. |
470
+ | `weight` | `"regular" \| "plus"` | `"plus"` | Font weight. |
471
+ | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
472
+
473
+ Accepts every other standard `<label>` prop (`htmlFor`, `ref`, ...).
474
+
268
475
  ### Select
269
476
 
270
477
  A native-select replacement built on Base UI's `Select`, styled to always drop down below the trigger (never flips above it) and to show the selected item's **label** in the trigger — not its raw `value`.
@@ -335,7 +542,7 @@ Also accepts every other Base UI `Select.Trigger` prop (`disabled`, `ref`, ...).
335
542
 
336
543
  | Prop | Type | Default | Description |
337
544
  | ----------- | ------------ | ------------ | --------------------------------------- |
338
- | `value` | `any` | —(required | The value submitted/selected. |
545
+ | `value` | `any` | — (required) | The value submitted/selected. |
339
546
  | `disabled` | `boolean` | `false` | Disables this option. |
340
547
 
341
548
  `children` is used both as the visible label and (automatically) as the text shown in the trigger once selected.
@@ -350,8 +557,208 @@ A thin horizontal divider between groups of items. No special props beyond stand
350
557
  <Select.Item value="orange">Orange</Select.Item>
351
558
  ```
352
559
 
560
+ ### Text
561
+
562
+ The base typography primitive for body copy — small/base/large sizes, two weights, two font families, and two line-height densities. [`Label`](#label) and [`Hint`](#hint) are both built on top of it.
563
+
564
+ ```tsx
565
+ import { Text } from "@florianke/components";
566
+
567
+ <Text>Base text</Text>
568
+ <Text size="small">Small text</Text>
569
+ <Text size="large">Large text</Text>
570
+
571
+ <Text weight="plus">Slightly bolder</Text>
572
+ <Text family="mono">Monospaced</Text>
573
+ <Text leading="compact">Tighter line height</Text>
574
+
575
+ {/* Render as a different element */}
576
+ <Text as="span">Inline text</Text>
577
+ <Text as="div">Block text</Text>
578
+ <Text as="label" htmlFor="name">A label</Text>
579
+ ```
580
+
581
+ | Prop | Type | Default | Description |
582
+ | ----------- | --------------------------------------------- | --------- | --------------------------------------------- |
583
+ | `as` | `"p" \| "span" \| "div" \| "label"` | `"p"` | The rendered HTML element. |
584
+ | `size` | `"small" \| "base" \| "large"` | `"base"` | Type scale. |
585
+ | `weight` | `"regular" \| "plus"` | `"regular"` | Font weight. |
586
+ | `family` | `"sans" \| "mono"` | `"sans"` | Font family. |
587
+ | `leading` | `"normal" \| "compact"` | `"normal"` | Line-height density. |
588
+ | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
589
+
590
+ Accepts every other standard HTML attribute for whichever element `as` renders.
591
+
592
+ ### Toast
593
+
594
+ Transient notifications, built on Base UI's `Toast`. Needs a `Toast.Provider` (already included if you use [`Providers`](#providers)) and a rendered `Toast.Viewport` + `Toast.List` somewhere in the tree (also already included in `Providers`).
595
+
596
+ ```tsx
597
+ "use client";
598
+
599
+ import { Button, Toast } from "@florianke/components";
600
+
601
+ function CreateToastButton() {
602
+ const toastManager = Toast.useToastManager();
603
+
604
+ return (
605
+ <Button
606
+ onClick={() =>
607
+ toastManager.add({
608
+ type: "success", // "success" | "error" | "warning" | "info" | undefined
609
+ title: "Changes saved",
610
+ description: "Your changes have been saved successfully.",
611
+ })
612
+ }
613
+ >
614
+ Save
615
+ </Button>
616
+ );
617
+ }
618
+ ```
619
+
620
+ If you're not using [`Providers`](#providers), set it up manually once near the root of your app:
621
+
622
+ ```tsx
623
+ <Toast.Provider>
624
+ {children}
625
+ <Toast.Viewport>
626
+ <Toast.List />
627
+ </Toast.Viewport>
628
+ </Toast.Provider>
629
+ ```
630
+
631
+ - `Toast.List` renders every active toast, automatically stacked/peeking, with the correct icon and color for `type`.
632
+ - The four `type` variants (`success`, `error`, `warning`, `info`) each get a distinct icon shape (not just a color), so they stay distinguishable for colorblind users too.
633
+ - `Toast.useToastManager()` (also exported standalone as `useToastManager`) returns `{ toasts, add, close, update, promise }` — see [Base UI's Toast docs](https://base-ui.com/react/components/toast) for the full manager API.
634
+
635
+ #### `Toast.Provider`
636
+
637
+ Forwards all props to Base UI's `Toast.Provider`.
638
+
639
+ #### `Toast.Viewport`
640
+
641
+ The fixed-position container toasts are portaled into (bottom-right corner by default). Forwards all props to Base UI's `Toast.Viewport`.
642
+
643
+ #### `Toast.List`
644
+
645
+ No props — renders the current toasts from `useToastManager()`. Use this unless you need full manual control over each toast's markup (in which case, compose `Toast.Root` / `Toast.Content` / `Toast.Icon` / `Toast.Title` / `Toast.Description` / `Toast.Close` yourself).
646
+
647
+ #### `Toast.Close`
648
+
649
+ Forwards all props to Base UI's `Toast.Close`. Defaults to rendering as our own `Button` with `variant="outline" size="sm"`.
650
+
651
+ ### Tooltip
652
+
653
+ A short, single-line (up to `max-w-64`, then wraps) hint shown on hover/focus, built on Base UI's `Tooltip`. Needs a `Tooltip.Provider` (already included if you use [`Providers`](#providers)) as an ancestor.
654
+
655
+ ```tsx
656
+ import { Button, Tooltip } from "@florianke/components";
657
+
658
+ <Tooltip.Provider>
659
+ <Tooltip>
660
+ <Tooltip.Trigger render={<Button variant="ghost" size="sm" />}>
661
+ <BoldIcon />
662
+ </Tooltip.Trigger>
663
+ <Tooltip.Content>Bold</Tooltip.Content>
664
+ </Tooltip>
665
+ </Tooltip.Provider>
666
+ ```
667
+
668
+ #### `Tooltip.Provider`
669
+
670
+ | Prop | Type | Default | Description |
671
+ | ------------ | ----------- | ------- | ---------------------------------------------------------------------- |
672
+ | `delay` | `number` | `150` | Milliseconds before a tooltip opens (Base UI's own default is `600`). |
673
+ | `closeDelay` | `number` | `0` | Milliseconds before a tooltip closes. |
674
+
675
+ Groups all tooltips underneath it so that once one is open, adjacent ones open instantly (Base UI's built-in behavior).
676
+
677
+ #### `Tooltip` (root)
678
+
679
+ Forwards all props to Base UI's `Tooltip.Root`.
680
+
681
+ #### `Tooltip.Trigger`
682
+
683
+ Forwards all props to Base UI's `Tooltip.Trigger` — most commonly `render`.
684
+
685
+ #### `Tooltip.Content`
686
+
687
+ | Prop | Type | Default | Description |
688
+ | ------------- | --------------------------------------------------- | --------- | ---------------------------------------------------- |
689
+ | `side` | `"top" \| "bottom" \| "left" \| "right"` | `undefined` (Base UI default: `"top"`) | Preferred side relative to the trigger. |
690
+ | `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment along that side. |
691
+ | `sideOffset` | `number` | `8` | Gap (px) between the trigger and the tooltip. |
692
+ | `alignOffset` | `number` | `undefined` | Extra offset along the alignment axis. |
693
+
353
694
  ## Hooks
354
695
 
696
+ ### useConfirm
697
+
698
+ An imperative confirmation dialog — `await` a promise instead of managing dialog open state yourself. Needs a `ConfirmProvider` as an ancestor (already included if you use [`Providers`](#providers)).
699
+
700
+ ```tsx
701
+ "use client";
702
+
703
+ import { Button, useConfirm } from "@florianke/components";
704
+
705
+ function DiscardButton() {
706
+ const confirm = useConfirm();
707
+
708
+ async function handleClick() {
709
+ const confirmed = await confirm({
710
+ title: "Discard draft?",
711
+ description: "You can't undo this action.",
712
+ confirmText: "Discard",
713
+ variant: "danger",
714
+ });
715
+ if (confirmed) {
716
+ // ...discard
717
+ }
718
+ }
719
+
720
+ return (
721
+ <Button variant="destructive" onClick={handleClick}>
722
+ Discard draft
723
+ </Button>
724
+ );
725
+ }
726
+ ```
727
+
728
+ For anything more sensitive than a plain yes/no (e.g. deleting an account or a production resource), require the user to type a matching string before the confirm button enables, via `verificationText`:
729
+
730
+ ```tsx
731
+ const confirmed = await confirm({
732
+ title: "Delete account",
733
+ description: `Type "${entityName}" to confirm.`,
734
+ confirmText: "Delete",
735
+ variant: "danger",
736
+ verificationText: entityName,
737
+ });
738
+ ```
739
+
740
+ If you're not using [`Providers`](#providers), set it up manually once near the root of your app:
741
+
742
+ ```tsx
743
+ import { ConfirmProvider } from "@florianke/components";
744
+
745
+ <ConfirmProvider>{children}</ConfirmProvider>
746
+ ```
747
+
748
+ `useConfirm(): (options?: ConfirmOptions) => Promise<boolean>` — resolves `true` when confirmed, `false` when cancelled (Escape, backdrop click, or the Cancel button).
749
+
750
+ `ConfirmOptions`:
751
+
752
+ | Option | Type | Default | Description |
753
+ | --------------------------- | ------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------- |
754
+ | `title` | `React.ReactNode` | `"Are you sure?"` | Dialog title. |
755
+ | `description` | `React.ReactNode` | `undefined` | Dialog description. |
756
+ | `confirmText` | `string` | `"Confirm"` | Label of the confirming button. |
757
+ | `cancelText` | `string` | `"Cancel"` | Label of the cancelling button. |
758
+ | `variant` | `"default" \| "danger"` | `"default"` | `"danger"` renders the confirm button with `Button`'s `destructive` variant. |
759
+ | `verificationText` | `string` | `undefined` | When set, requires the user to type this exact text before confirming. |
760
+ | `verificationInstruction` | `React.ReactNode` | `Type <b>{verificationText}</b> to confirm.` | Custom label shown above the verification input. |
761
+
355
762
  ### useInitials
356
763
 
357
764
  Derives up to two initials from a full name — the first letter of the first word and the first letter of the last word (falls back to a single letter for one-word names). This is what powers `Avatar`'s fallback, and is exported standalone in case you need initials somewhere else in your app (e.g. a table row, a comment list).
@@ -373,19 +780,48 @@ useInitials("Max Peter Mustermann"); // "MM" (first + last word)
373
780
 
374
781
  ## Package Exports
375
782
 
376
- Every component (and the `useInitials` hook) is available from the main entry point, and also as its own subpath for more granular imports/bundling:
783
+ Every component (and hook) is available from the main entry point, and also as its own subpath for more granular imports/bundling:
377
784
 
378
785
  ```tsx
379
786
  // Everything, from the main entry
380
- import { Avatar, Button, Card, DropdownMenu, Input, Select, useInitials } from "@florianke/components";
787
+ import {
788
+ AlertDialog,
789
+ Avatar,
790
+ Button,
791
+ Card,
792
+ ConfirmProvider,
793
+ DropdownMenu,
794
+ Field,
795
+ Heading,
796
+ Hint,
797
+ Input,
798
+ Label,
799
+ Providers,
800
+ Select,
801
+ Text,
802
+ Toast,
803
+ Tooltip,
804
+ useConfirm,
805
+ useInitials,
806
+ } from "@florianke/components";
381
807
 
382
808
  // Or, individually
809
+ import { AlertDialog } from "@florianke/components/alert-dialog";
383
810
  import { Avatar } from "@florianke/components/avatar";
384
811
  import { Button } from "@florianke/components/button";
385
812
  import { Card } from "@florianke/components/card";
386
813
  import { DropdownMenu } from "@florianke/components/dropdown-menu";
814
+ import { Field } from "@florianke/components/field";
815
+ import { Heading } from "@florianke/components/heading";
816
+ import { Hint } from "@florianke/components/hint";
387
817
  import { Input } from "@florianke/components/input";
818
+ import { Label } from "@florianke/components/label";
819
+ import { Providers } from "@florianke/components/providers";
388
820
  import { Select } from "@florianke/components/select";
821
+ import { Text } from "@florianke/components/text";
822
+ import { Toast } from "@florianke/components/toast";
823
+ import { Tooltip } from "@florianke/components/tooltip";
824
+ import { ConfirmProvider, useConfirm } from "@florianke/components/hooks/use-confirm";
389
825
  import { useInitials } from "@florianke/components/hooks/use-initials";
390
826
 
391
827
  import "@florianke/components/styles.css";
@@ -1,35 +1,6 @@
1
- import * as React from "react";
2
- import { AlertDialog as AlertDialog$1 } from "@base-ui/react/alert-dialog";
1
+ import { AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogRoot, AlertDialogTitle, AlertDialogTrigger } from "./components/alert-dialog/alert-dialog.mjs";
3
2
 
4
- //#region src/components/alert-dialog/alert-dialog.d.ts
5
- type AlertDialogRootProps = React.ComponentPropsWithoutRef<typeof AlertDialog$1.Root>;
6
- declare function AlertDialogRoot(props: AlertDialogRootProps): React.JSX.Element;
7
- type AlertDialogTriggerProps = React.ComponentPropsWithoutRef<typeof AlertDialog$1.Trigger>;
8
- declare function AlertDialogTrigger({
9
- className,
10
- ...props
11
- }: AlertDialogTriggerProps): React.JSX.Element;
12
- type AlertDialogContentProps = React.ComponentPropsWithoutRef<typeof AlertDialog$1.Popup>;
13
- declare function AlertDialogContent({
14
- className,
15
- children,
16
- ...props
17
- }: AlertDialogContentProps): React.JSX.Element;
18
- type AlertDialogTitleProps = React.ComponentPropsWithoutRef<typeof AlertDialog$1.Title>;
19
- declare function AlertDialogTitle({
20
- className,
21
- ...props
22
- }: AlertDialogTitleProps): React.JSX.Element;
23
- type AlertDialogDescriptionProps = React.ComponentPropsWithoutRef<typeof AlertDialog$1.Description>;
24
- declare function AlertDialogDescription({
25
- className,
26
- ...props
27
- }: AlertDialogDescriptionProps): React.JSX.Element;
28
- type AlertDialogCloseProps = React.ComponentPropsWithoutRef<typeof AlertDialog$1.Close>;
29
- declare function AlertDialogClose({
30
- render,
31
- ...props
32
- }: AlertDialogCloseProps): React.JSX.Element;
3
+ //#region src/components/alert-dialog/index.d.ts
33
4
  declare const AlertDialog: typeof AlertDialogRoot & {
34
5
  Trigger: typeof AlertDialogTrigger;
35
6
  Content: typeof AlertDialogContent;
@@ -1 +1 @@
1
- {"version":3,"file":"alert-dialog.d.mts","names":[],"sources":["../src/components/alert-dialog/alert-dialog.tsx"],"mappings":";;;;KAQK,oBAAA,GAAuB,KAAA,CAAM,wBAAA,QACzB,aAAA,CAAgB,IAAA;AAAA,iBAGhB,eAAA,CAAgB,KAAA,EAAO,oBAAA,GAAoB,KAAA,CAAA,GAAA,CAAA,OAAA;AAAA,KAI/C,uBAAA,GAA0B,KAAA,CAAM,wBAAA,QAC5B,aAAA,CAAgB,OAAA;AAAA,iBAGhB,kBAAA,CAAA;EAAqB,SAAA;EAAA,GAAc;AAAA,GAAS,uBAAA,GAAuB,KAAA,CAAA,GAAA,CAAA,OAAA;AAAA,KAYvE,uBAAA,GAA0B,KAAA,CAAM,wBAAA,QAC5B,aAAA,CAAgB,KAAA;AAAA,iBAGhB,kBAAA,CAAA;EACP,SAAA;EACA,QAAA;EAAA,GACG;AAAA,GACF,uBAAA,GAAuB,KAAA,CAAA,GAAA,CAAA,OAAA;AAAA,KA0BrB,qBAAA,GAAwB,KAAA,CAAM,wBAAA,QAC1B,aAAA,CAAgB,KAAA;AAAA,iBAGhB,gBAAA,CAAA;EAAmB,SAAA;EAAA,GAAc;AAAA,GAAS,qBAAA,GAAqB,KAAA,CAAA,GAAA,CAAA,OAAA;AAAA,KASnE,2BAAA,GAA8B,KAAA,CAAM,wBAAA,QAChC,aAAA,CAAgB,WAAA;AAAA,iBAGhB,sBAAA,CAAA;EACP,SAAA;EAAA,GACG;AAAA,GACF,2BAAA,GAA2B,KAAA,CAAA,GAAA,CAAA,OAAA;AAAA,KAUzB,qBAAA,GAAwB,KAAA,CAAM,wBAAA,QAC1B,aAAA,CAAgB,KAAA;AAAA,iBAGhB,gBAAA,CAAA;EAAmB,MAAA;EAAA,GAAW;AAAA,GAAS,qBAAA,GAAqB,KAAA,CAAA,GAAA,CAAA,OAAA;AAAA,cASxD,WAAA,SAAW,eAAA"}
1
+ {"version":3,"file":"alert-dialog.d.mts","names":[],"sources":["../src/components/alert-dialog/index.ts"],"mappings":";;;cAea,WAAA,SAAW,eAAA"}