@florianke/components 0.0.1 → 0.0.2

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 (2) hide show
  1. package/README.md +396 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,396 @@
1
+ # @florianke/components
2
+
3
+ A small, opinionated React component library built on top of [Base UI](https://base-ui.com) primitives and [Tailwind CSS v4](https://tailwindcss.com). Every component is styled with CSS custom properties so your app's light and dark themes stay consistent across the whole kit.
4
+
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
+ - **Themeable** — all colors are CSS variables (`--background`, `--foreground`, `--ring`, ...), so you can restyle the whole library by overriding a handful of custom properties.
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.
9
+
10
+ ## Table of Contents
11
+
12
+ - [Requirements](#requirements)
13
+ - [Installation](#installation)
14
+ - [Setup](#setup)
15
+ - [Quick Start](#quick-start)
16
+ - [Theming](#theming)
17
+ - [Components](#components)
18
+ - [Avatar](#avatar)
19
+ - [Button](#button)
20
+ - [Card](#card)
21
+ - [DropdownMenu](#dropdownmenu)
22
+ - [Input](#input)
23
+ - [Select](#select)
24
+ - [Hooks](#hooks)
25
+ - [useInitials](#useinitials)
26
+ - [Package Exports](#package-exports)
27
+ - [License](#license)
28
+
29
+ ## Requirements
30
+
31
+ - React 19+ and React DOM 19+ (peer dependencies)
32
+ - Tailwind CSS v4 configured in your own project (this library ships raw Tailwind utility classes — your app's Tailwind build is what turns them into actual CSS)
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ npm install @florianke/components
38
+ # or
39
+ pnpm add @florianke/components
40
+ # or
41
+ yarn add @florianke/components
42
+ ```
43
+
44
+ `react` and `react-dom` are peer dependencies — make sure your project already has them installed (any modern React 19 app does).
45
+
46
+ ## Setup
47
+
48
+ ### 1. Import the base styles
49
+
50
+ The package ships a single stylesheet with the design tokens (light/dark color variables) and the `@import "tailwindcss"` bootstrap. Import it once, at the root of your app:
51
+
52
+ ```ts
53
+ // e.g. src/app/globals.css, or your root layout/entry file
54
+ import "@florianke/components/styles.css";
55
+ ```
56
+
57
+ Or, if you're importing it from a CSS file instead of a JS/TS entry point:
58
+
59
+ ```css
60
+ @import "tailwindcss";
61
+ @import "@florianke/components/styles.css";
62
+ ```
63
+
64
+ ### 2. Point Tailwind at the package
65
+
66
+ Because the components' class names live inside the published JavaScript, your Tailwind build needs to scan the package's compiled output so it actually generates the corresponding CSS. Add a `@source` directive next to your other Tailwind directives:
67
+
68
+ ```css
69
+ @import "tailwindcss";
70
+ @import "@florianke/components/styles.css";
71
+ @source "../node_modules/@florianke/components/dist";
72
+ ```
73
+
74
+ (Adjust the relative path so it points at your `node_modules/@florianke/components/dist` folder from wherever your CSS file lives.)
75
+
76
+ ### 3. Dark mode
77
+
78
+ Dark mode is automatic via `prefers-color-scheme`, and can be forced by toggling a `.dark` (or `.light`, to force light) class anywhere up the DOM tree — typically on `<html>`:
79
+
80
+ ```tsx
81
+ <html className="dark">
82
+ ```
83
+
84
+ ## Quick Start
85
+
86
+ ```tsx
87
+ "use client";
88
+
89
+ import { Avatar, Button, Card, Input } from "@florianke/components";
90
+
91
+ export default function Example() {
92
+ return (
93
+ <Card className="flex w-80 flex-col gap-4 p-6">
94
+ <div className="flex items-center gap-3">
95
+ <Avatar name="Ada Lovelace" />
96
+ <span className="text-[14px] font-medium text-(--foreground)">
97
+ Ada Lovelace
98
+ </span>
99
+ </div>
100
+ <Input placeholder="Type something..." />
101
+ <Button>Continue</Button>
102
+ </Card>
103
+ );
104
+ }
105
+ ```
106
+
107
+ ## Theming
108
+
109
+ Every component reads its colors from CSS custom properties, defined once in `styles.css` and re-defined inside `.dark`:
110
+
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 |
122
+
123
+ Override any of these in your own CSS (after importing the package's stylesheet) to reskin the whole library:
124
+
125
+ ```css
126
+ :root {
127
+ --ring: oklch(0.6 0.2 25); /* e.g. switch the focus color to red */
128
+ }
129
+ ```
130
+
131
+ ## Components
132
+
133
+ ### Avatar
134
+
135
+ 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).
136
+
137
+ ```tsx
138
+ import { Avatar } from "@florianke/components";
139
+
140
+ <Avatar name="Ada Lovelace" src="/ada.jpg" />
141
+ <Avatar name="Ada Lovelace" /> {/* no src → shows "AL" immediately */}
142
+ <Avatar name="Ada Lovelace" size="small" variant="squared" />
143
+ ```
144
+
145
+ | 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). |
148
+ | `src` | `string` | `undefined` | Image URL. When omitted, the initials fallback is shown. |
149
+ | `variant` | `"rounded" \| "squared"` | `"rounded"` | Shape of the avatar. |
150
+ | `size` | `"small" \| "base" \| "large"` | `"base"` | Size of the avatar. |
151
+ | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
152
+
153
+ Also accepts every other prop of Base UI's `Avatar.Root` (e.g. `id`, `ref`, ...).
154
+
155
+ ### Button
156
+
157
+ ```tsx
158
+ import { Button } from "@florianke/components";
159
+
160
+ <Button>Primary</Button>
161
+ <Button variant="secondary">Secondary</Button>
162
+ <Button variant="outline">Outline</Button>
163
+ <Button variant="ghost">Ghost</Button>
164
+ <Button variant="link">Link</Button>
165
+
166
+ <Button size="sm">Small</Button>
167
+ <Button size="md">Medium</Button>
168
+
169
+ <Button disabled>Disabled</Button>
170
+
171
+ {/* With an icon */}
172
+ <Button>
173
+ <svg className="size-4" /* ... */ />
174
+ Continue
175
+ </Button>
176
+ ```
177
+
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. |
184
+
185
+ Also accepts every other native `<button>` prop (`onClick`, `type`, `ref`, ...), since it's built on Base UI's `Button`.
186
+
187
+ ### Card
188
+
189
+ A simple, styled container — a `<div>` with a border, background, and rounded corners tied to the theme tokens. Compose it freely with your own layout classes.
190
+
191
+ ```tsx
192
+ import { Card } from "@florianke/components";
193
+
194
+ <Card className="w-72 p-4">
195
+ <p className="text-[14px] text-(--card-foreground)">
196
+ Card content goes here.
197
+ </p>
198
+ </Card>
199
+ ```
200
+
201
+ | Prop | Type | Default | Description |
202
+ | ----------- | ----------- | ----------- | --------------------------------------------- |
203
+ | `className` | `string` | `undefined` | Extra classes, merged with the defaults. |
204
+
205
+ Accepts every other standard `<div>` prop.
206
+
207
+ ### DropdownMenu
208
+
209
+ A floating action menu (e.g. "Sort by", context actions), built on Base UI's `Menu`. Currently exposes `Trigger`, `Content`, and plain `Item` — no submenus, checkboxes, or radio groups yet.
210
+
211
+ ```tsx
212
+ import { Button, DropdownMenu } from "@florianke/components";
213
+
214
+ <DropdownMenu>
215
+ <DropdownMenu.Trigger render={<Button variant="outline" size="sm" />}>
216
+ Sort by
217
+ </DropdownMenu.Trigger>
218
+ <DropdownMenu.Content>
219
+ <DropdownMenu.Item>Name</DropdownMenu.Item>
220
+ <DropdownMenu.Item>Date modified</DropdownMenu.Item>
221
+ <DropdownMenu.Item disabled>Size</DropdownMenu.Item>
222
+ </DropdownMenu.Content>
223
+ </DropdownMenu>
224
+ ```
225
+
226
+ Use the `render` prop (from Base UI) to render the trigger as any element you like — here it renders as our own `Button` instead of a plain `<button>`.
227
+
228
+ #### `DropdownMenu` (root)
229
+
230
+ Forwards all props to Base UI's `Menu.Root` — e.g. `open`, `defaultOpen`, `onOpenChange`, `modal`.
231
+
232
+ #### `DropdownMenu.Trigger`
233
+
234
+ Forwards all props to Base UI's `Menu.Trigger` — most commonly `render` (to swap the rendered element/component) and `disabled`.
235
+
236
+ #### `DropdownMenu.Content`
237
+
238
+ | Prop | Type | Default | Description |
239
+ | ------------- | --------------------------------------------------- | --------- | ---------------------------------------------------- |
240
+ | `side` | `"top" \| "bottom" \| "left" \| "right"` | `undefined` (Base UI default: `"bottom"`) | Preferred side relative to the trigger. |
241
+ | `align` | `"start" \| "center" \| "end"` | `"start"` | Alignment along that side. |
242
+ | `sideOffset` | `number` | `6` | Gap (px) between the trigger and the menu. |
243
+ | `alignOffset` | `number` | `undefined` | Extra offset along the alignment axis. |
244
+
245
+ #### `DropdownMenu.Item`
246
+
247
+ Forwards all props to Base UI's `Menu.Item` — most commonly `disabled` and `onClick`.
248
+
249
+ ### Input
250
+
251
+ ```tsx
252
+ import { Input } from "@florianke/components";
253
+
254
+ <Input placeholder="e.g. Ada Lovelace" />
255
+ <Input size="sm" placeholder="Small" />
256
+ <Input disabled placeholder="Can't edit this" />
257
+ <Input defaultValue="1920" />
258
+ ```
259
+
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. |
265
+
266
+ Also accepts every other native `<input>` prop (`value`, `onChange`, `placeholder`, `ref`, ...), except `size` which is overridden by the prop above.
267
+
268
+ ### Select
269
+
270
+ 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`.
271
+
272
+ ```tsx
273
+ import { Select } from "@florianke/components";
274
+
275
+ <Select>
276
+ <Select.Trigger placeholder="Choose a fruit" />
277
+ <Select.Content>
278
+ <Select.Item value="apple">Apple</Select.Item>
279
+ <Select.Item value="banana">Banana</Select.Item>
280
+ <Select.Separator />
281
+ <Select.Item value="orange">Orange</Select.Item>
282
+ </Select.Content>
283
+ </Select>
284
+
285
+ {/* Uncontrolled default value */}
286
+ <Select defaultValue="banana">
287
+ <Select.Trigger placeholder="Choose a fruit" />
288
+ <Select.Content>
289
+ <Select.Item value="apple">Apple</Select.Item>
290
+ <Select.Item value="banana">Banana</Select.Item>
291
+ </Select.Content>
292
+ </Select>
293
+
294
+ {/* Disabled item / disabled select */}
295
+ <Select>
296
+ <Select.Trigger placeholder="Choose a fruit" />
297
+ <Select.Content>
298
+ <Select.Item value="apple">Apple</Select.Item>
299
+ <Select.Item value="banana" disabled>Banana</Select.Item>
300
+ </Select.Content>
301
+ </Select>
302
+
303
+ <Select disabled>
304
+ <Select.Trigger placeholder="Choose a fruit" />
305
+ <Select.Content>
306
+ <Select.Item value="apple">Apple</Select.Item>
307
+ </Select.Content>
308
+ </Select>
309
+ ```
310
+
311
+ You never need to pass a separate `items` map — `Select` walks its own children at render time, collects every `Select.Item`'s `value`/label pair, and wires that up to Base UI automatically so the trigger displays "Banana" instead of `banana`.
312
+
313
+ #### `Select` (root)
314
+
315
+ Forwards all props to Base UI's `Select.Root` — e.g. `defaultValue`, `value`, `onValueChange`, `disabled`, `multiple`, `name`. Pass your own `items` map explicitly if you ever need to override the auto-derived one.
316
+
317
+ #### `Select.Trigger`
318
+
319
+ | Prop | Type | Default | Description |
320
+ | ------------- | ----------- | ------------ | --------------------------------------------- |
321
+ | `placeholder` | `string` | `undefined` | Shown (muted) when nothing is selected yet. |
322
+
323
+ Also accepts every other Base UI `Select.Trigger` prop (`disabled`, `ref`, ...).
324
+
325
+ #### `Select.Content`
326
+
327
+ | Prop | Type | Default | Description |
328
+ | ------------- | ------------ | ----------- | ------------------------------------------------------------------------ |
329
+ | `side` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | Defaults to opening below the trigger. Collision-based flipping is disabled, so the popup keeps its `side` even if it would overflow the viewport. |
330
+ | `align` | `"start" \| "center" \| "end"` | `"start"` | Alignment along that side. |
331
+ | `sideOffset` | `number` | `6` | Gap (px) between the trigger and the popup. |
332
+ | `alignOffset` | `number` | `undefined` | Extra offset along the alignment axis. |
333
+
334
+ #### `Select.Item`
335
+
336
+ | Prop | Type | Default | Description |
337
+ | ----------- | ------------ | ------------ | --------------------------------------- |
338
+ | `value` | `any` | —(required) | The value submitted/selected. |
339
+ | `disabled` | `boolean` | `false` | Disables this option. |
340
+
341
+ `children` is used both as the visible label and (automatically) as the text shown in the trigger once selected.
342
+
343
+ #### `Select.Separator`
344
+
345
+ A thin horizontal divider between groups of items. No special props beyond standard `<div>` props.
346
+
347
+ ```tsx
348
+ <Select.Item value="apple">Apple</Select.Item>
349
+ <Select.Separator />
350
+ <Select.Item value="orange">Orange</Select.Item>
351
+ ```
352
+
353
+ ## Hooks
354
+
355
+ ### useInitials
356
+
357
+ 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).
358
+
359
+ ```tsx
360
+ import { useInitials } from "@florianke/components";
361
+
362
+ function Example({ name }: { name: string }) {
363
+ const initials = useInitials(name);
364
+ return <span>{initials}</span>;
365
+ }
366
+
367
+ useInitials("Ada Lovelace"); // "AL"
368
+ useInitials("Cher"); // "C"
369
+ useInitials("Max Peter Mustermann"); // "MM" (first + last word)
370
+ ```
371
+
372
+ `useInitials(name: string): string` — must be called from within a Client Component, like any other React hook.
373
+
374
+ ## Package Exports
375
+
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:
377
+
378
+ ```tsx
379
+ // Everything, from the main entry
380
+ import { Avatar, Button, Card, DropdownMenu, Input, Select, useInitials } from "@florianke/components";
381
+
382
+ // Or, individually
383
+ import { Avatar } from "@florianke/components/avatar";
384
+ import { Button } from "@florianke/components/button";
385
+ import { Card } from "@florianke/components/card";
386
+ import { DropdownMenu } from "@florianke/components/dropdown-menu";
387
+ import { Input } from "@florianke/components/input";
388
+ import { Select } from "@florianke/components/select";
389
+ import { useInitials } from "@florianke/components/hooks/use-initials";
390
+
391
+ import "@florianke/components/styles.css";
392
+ ```
393
+
394
+ ## License
395
+
396
+ ISC
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@florianke/components",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "main": "./dist/index.mjs",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.mts",