@pinfold/react 0.0.0-stage → 0.2.0

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 (41) hide show
  1. package/README.md +58 -2
  2. package/accordion/lazy/package.json +6 -0
  3. package/accordion/package.json +6 -0
  4. package/dist/accordion/client.cjs +491 -0
  5. package/dist/accordion/client.d.cts +34 -0
  6. package/dist/accordion/client.d.ts +34 -0
  7. package/dist/accordion/client.js +490 -0
  8. package/dist/accordion/index.cjs +49 -0
  9. package/dist/accordion/index.d.cts +56 -0
  10. package/dist/accordion/index.d.ts +56 -0
  11. package/dist/accordion/index.js +14 -0
  12. package/dist/accordion/lazy.cjs +17 -0
  13. package/dist/accordion/lazy.d.cts +16 -0
  14. package/dist/accordion/lazy.d.ts +16 -0
  15. package/dist/accordion/lazy.js +16 -0
  16. package/dist/chunk-5ZMK623A.js +61 -0
  17. package/dist/chunk-BPPCPHHU.js +70 -0
  18. package/dist/chunk-DQWSDHRY.cjs +71 -0
  19. package/dist/chunk-Q232PTPW.js +115 -0
  20. package/dist/chunk-RJPT3NTP.cjs +62 -0
  21. package/dist/chunk-VPKVOJHA.cjs +116 -0
  22. package/dist/client.cjs +30 -0
  23. package/dist/client.d.cts +120 -0
  24. package/dist/client.d.ts +120 -0
  25. package/dist/client.js +29 -0
  26. package/dist/index.cjs +52 -0
  27. package/dist/index.d.cts +123 -0
  28. package/dist/index.d.ts +123 -0
  29. package/dist/index.js +10 -0
  30. package/dist/types-CkcgnWwL.d.cts +367 -0
  31. package/dist/types-CkcgnWwL.d.ts +367 -0
  32. package/dist/types-N0Yo4zGa.d.cts +374 -0
  33. package/dist/types-N0Yo4zGa.d.ts +374 -0
  34. package/dist/validate-EQHBH4NH.cjs +42 -0
  35. package/dist/validate-WKHQNNGD.js +41 -0
  36. package/llms-full.txt +140 -0
  37. package/llms.txt +26 -0
  38. package/package.json +92 -4
  39. package/registry/accordion.json +20 -0
  40. package/registry/registry.json +25 -0
  41. package/schema/accordion.json +903 -0
@@ -0,0 +1,374 @@
1
+ import * as React from 'react';
2
+ import { Slottable as Slottable$1, PropsBag, AccordionChangeDetails, PinfoldOrientation, PinfoldDir, AccordionMountPanels, PinfoldLazy, PinfoldSize, PinfoldTone, PinfoldRadius, Length, AccordionFieldMap } from '@pinfold/core';
3
+
4
+ /**
5
+ * A component that is also its own controller.
6
+ *
7
+ * const Accordion = useAccordion(config);
8
+ * return <Accordion className="mt-6" />; // renders
9
+ * Accordion.toggle('faq-1'); // controls
10
+ * <Accordion.Panel id="faq-1" /> // composes
11
+ */
12
+ type PinfoldComponent<Api, Props> = ((props: Props) => React.ReactElement | null) & Api & {
13
+ displayName?: string;
14
+ };
15
+ type Part = (props: any) => React.ReactElement | null;
16
+ /**
17
+ * Turn a render function plus an API object (and optional parts) into a
18
+ * stable component.
19
+ *
20
+ * Identity stability: a component created fresh on every render is a *new
21
+ * element type* to React, which remounts the whole subtree on each parent
22
+ * update, losing focus, selection, scroll position and running transitions.
23
+ * So the component and its parts are created once and read the current
24
+ * render through a ref.
25
+ *
26
+ * Freshness: because the identity never changes, a memoising parent (the
27
+ * React Compiler, `React.memo`, a cached element) may skip re-rendering it.
28
+ * So every hook render bumps a version, and after commit the component and
29
+ * each part re-render through `useSyncExternalStore` if they have not yet
30
+ * seen it. In the ordinary case they already rendered with the parent, the
31
+ * snapshot matches, and React does no extra work.
32
+ */
33
+ declare function usePinfoldComponent<Api extends object, Props extends object, Parts extends {
34
+ [K in keyof Parts]: Part;
35
+ } = {}>(displayName: string, api: Api, render: (props: Props) => React.ReactElement | null, buildParts?: () => Parts): PinfoldComponent<Api & Parts, Props>;
36
+
37
+ /**
38
+ * Blocks type inference from a position, so a value there is checked against what was inferred
39
+ * elsewhere instead of widening it (a wrong id is an error, not a new id). TypeScript 5.4 has
40
+ * this built in; this form works back to 4.7, the published floor.
41
+ */
42
+ type NoInfer<T> = [T][T extends any ? 0 : never];
43
+ /** A config field that is a node, or a function of live state returning one. */
44
+ type Slottable<State, T = React.ReactNode> = Slottable$1<State, T>;
45
+ /**
46
+ * Per-slot escape hatches. Every Pinfold component exposes the same four, so
47
+ * learning one component teaches all of them.
48
+ *
49
+ * - `classNames` — append a class to one slot.
50
+ * - `slotProps` — merge arbitrary DOM props into one slot (event handlers chain).
51
+ * - `styles` — inline style for one slot.
52
+ * - `render` — replace one slot's element entirely, keeping its behaviour props.
53
+ */
54
+ interface SlotOverrides<Slot extends string, State = unknown> {
55
+ classNames?: Partial<Record<Slot, string>>;
56
+ styles?: Partial<Record<Slot, React.CSSProperties>>;
57
+ slotProps?: Partial<Record<Slot, PropsBag>>;
58
+ render?: Partial<Record<Slot, SlotRenderer<State>>>;
59
+ }
60
+ type SlotRenderer<State = unknown> = (args: {
61
+ /** Behaviour props (ARIA, handlers, refs). Spread these or accessibility breaks. */
62
+ props: PropsBag;
63
+ /** Live component state for this slot. */
64
+ state: State;
65
+ children: React.ReactNode;
66
+ }) => React.ReactNode;
67
+
68
+ type AccordionSlot = 'root' | 'item' | 'header' | 'trigger' | 'icon' | 'label' | 'title' | 'description' | 'badge' | 'endIcon' | 'indicator' | 'panel' | 'content';
69
+ type AccordionVariant = 'solid' | 'outline' | 'separated' | 'filled' | 'ghost';
70
+ type AccordionIndicator = 'chevron' | 'plus' | 'caret' | 'arrow' | 'none';
71
+ /** A radius preset (the theme scale) or a length: `20` is px, strings pass through. */
72
+ type AccordionRadius = PinfoldRadius | number | (string & {});
73
+ /** A spacing preset or a length: `12` is px, strings pass through. */
74
+ type AccordionGap = 'none' | 'sm' | 'md' | 'lg' | number | (string & {});
75
+ /**
76
+ * The ids of a list of rows, e.g. for typed controlled state:
77
+ * `useState<AccordionItemId<typeof items>[]>([])`.
78
+ */
79
+ type AccordionItemId<Items extends ReadonlyArray<{
80
+ id?: string;
81
+ }>> = NonNullable<Items[number]['id']>;
82
+ /** Runtime state of a single row. Passed to every function-valued config field. */
83
+ interface AccordionItemState<Id extends string = string> {
84
+ id: Id;
85
+ index: number;
86
+ open: boolean;
87
+ disabled: boolean;
88
+ config: AccordionItemConfig<Id>;
89
+ triggerId: string;
90
+ panelId: string;
91
+ }
92
+ /** Anything renderable in a row: a node, or a function of the row's live state. */
93
+ type AccordionNode<Id extends string = string> = Slottable<AccordionItemState<Id>>;
94
+ /** One row of the accordion. This is the JSON a consumer — or a model — writes. */
95
+ interface AccordionItemConfig<Id extends string = string> {
96
+ /** Stable key. Falls back to the array index when omitted. Give every row one to get typed ids. */
97
+ id?: Id;
98
+ title: AccordionNode<Id>;
99
+ content?: AccordionNode<Id>;
100
+ /** Secondary line under the title. */
101
+ description?: AccordionNode<Id>;
102
+ /** Leading visual: a node, or a string (an emoji, or an icon-font name with `classNames.icon`). Overrides the accordion-wide `icon`. */
103
+ icon?: AccordionNode<Id>;
104
+ /** Trailing chip rendered before the indicator. */
105
+ badge?: AccordionNode<Id>;
106
+ /** Trailing visual just before the indicator. Overrides the accordion-wide `endIcon`. */
107
+ endIcon?: AccordionNode<Id>;
108
+ /** Overrides the accordion-wide indicator for this row only. */
109
+ indicator?: AccordionIndicator | AccordionNode<Id>;
110
+ disabled?: boolean;
111
+ /** Open on first render (uncontrolled only). */
112
+ defaultOpen?: boolean;
113
+ /** Per-row class and style overrides, same slot names as the root config. */
114
+ classNames?: Partial<Record<AccordionSlot, string>>;
115
+ styles?: Partial<Record<AccordionSlot, React.CSSProperties>>;
116
+ }
117
+ /** Text overflow for one slot: wrap, one line with an ellipsis, or clamp to n lines. */
118
+ type AccordionOverflow = 'wrap' | 'truncate' | {
119
+ lines: number;
120
+ };
121
+ type AccordionValueDetails<Id extends string = string> = Omit<AccordionChangeDetails<Event | React.SyntheticEvent>, 'id'> & {
122
+ /** The row that triggered the change. Absent for `openAll` / `closeAll`. */
123
+ id?: Id;
124
+ };
125
+ interface AccordionBehaviourConfig<Id extends string = string> {
126
+ /**
127
+ * `single` closes siblings on open.
128
+ * @default 'single'
129
+ */
130
+ mode?: 'single' | 'multiple';
131
+ /**
132
+ * Allow closing the last open row. `false` keeps at least one row open, in either mode.
133
+ * @default true
134
+ */
135
+ collapsible?: boolean;
136
+ /**
137
+ * Controlled open ids. Any `string[]` state works (`useState<string[]>`); for typed state use
138
+ * `useState<AccordionItemId<typeof items>[]>`.
139
+ */
140
+ value?: ReadonlyArray<string>;
141
+ /** Initially open ids (uncontrolled). */
142
+ defaultValue?: ReadonlyArray<NoInfer<Id>>;
143
+ /** `details.reason`: 'click' | 'keyboard' | 'api' | 'find-in-page' | 'hash'. */
144
+ onValueChange?: (value: Id[], details: AccordionValueDetails<Id>) => void;
145
+ /** Return `false` to block a change (e.g. unsaved edits in the open panel). */
146
+ onBeforeChange?: (details: AccordionValueDetails<Id>) => boolean | void;
147
+ /** Fires for one item, after `onValueChange`. */
148
+ onItemToggle?: (id: Id, open: boolean) => void;
149
+ /**
150
+ * Arrow-key axis for moving between headers.
151
+ * @default 'vertical'
152
+ */
153
+ orientation?: PinfoldOrientation;
154
+ /** Text direction. Defaults to the provider. */
155
+ dir?: PinfoldDir;
156
+ /**
157
+ * Wrap focus at the ends.
158
+ * @default true
159
+ */
160
+ loop?: boolean;
161
+ /** Prefix for generated ids — set it for deterministic SSR markup. */
162
+ id?: string;
163
+ /**
164
+ * Disables every row. Rows stay focusable.
165
+ * @default false
166
+ */
167
+ disabled?: boolean;
168
+ /** Disables these rows by id, for data you cannot annotate row by row. */
169
+ disabledIds?: ReadonlyArray<NoInfer<Id>>;
170
+ /**
171
+ * Closed panels use `hidden="until-found"`, so the browser's find-in-page
172
+ * searches them and opens the match.
173
+ * @default true
174
+ */
175
+ hiddenUntilFound?: boolean;
176
+ /**
177
+ * When panel content is in the DOM. `always` keeps it searchable and indexable;
178
+ * `open` mounts only open panels; `first-open` mounts on first open and keeps it.
179
+ * @default 'always'
180
+ */
181
+ mountPanels?: AccordionMountPanels;
182
+ /** @deprecated Use `mountPanels: 'open'`. */
183
+ unmountClosed?: boolean;
184
+ /**
185
+ * `#id` in the URL opens that row and scrolls to it; opening a row updates the hash.
186
+ * @default false
187
+ */
188
+ syncWithHash?: boolean;
189
+ /**
190
+ * Code loading for the `/lazy` entry. The accordion is light, so `auto` loads eagerly.
191
+ * @default 'auto'
192
+ */
193
+ lazy?: PinfoldLazy;
194
+ }
195
+ /**
196
+ * Explicit sizing. Numbers are px, strings pass through (`'50%'`, `'18rem'`,
197
+ * `'clamp(20rem, 40vw, 32rem)'`). Each key overrides the value the `size`
198
+ * preset would have chosen, so you can take one dimension and leave the rest.
199
+ */
200
+ interface AccordionDimensions {
201
+ width?: Length;
202
+ minWidth?: Length;
203
+ maxWidth?: Length;
204
+ height?: Length;
205
+ maxHeight?: Length;
206
+ /** Space between rows. Overrides `gap`. */
207
+ gap?: Length;
208
+ /** Trigger padding. */
209
+ paddingX?: Length;
210
+ paddingY?: Length;
211
+ /** Panel padding. Defaults to the trigger padding. */
212
+ panelPaddingX?: Length;
213
+ panelPaddingY?: Length;
214
+ /** Cap the open panel and let it scroll past that. */
215
+ panelMaxHeight?: Length;
216
+ fontSize?: Length;
217
+ titleSize?: Length;
218
+ descriptionSize?: Length;
219
+ iconSize?: Length;
220
+ indicatorSize?: Length;
221
+ borderWidth?: Length;
222
+ /** Container corner radius as a length. Overrides `radius`. */
223
+ radius?: Length;
224
+ /** Row corner radius as a length. Overrides `itemRadius`. */
225
+ itemRadius?: Length;
226
+ /** Open/close transition duration, e.g. `'320ms'`. */
227
+ duration?: Length;
228
+ }
229
+ interface AccordionAppearanceConfig<Id extends string = string> {
230
+ /** @default 'solid' */
231
+ variant?: AccordionVariant;
232
+ /** @default 'md' */
233
+ size?: PinfoldSize;
234
+ /** @default 'primary' */
235
+ tone?: PinfoldTone;
236
+ /** Corner radius of the container (the element that holds the rows). Unset: the theme radius. */
237
+ radius?: AccordionRadius;
238
+ /** Corner radius of each row. Unset: follows `radius` (square rows inside a solid or outline card). */
239
+ itemRadius?: AccordionRadius;
240
+ /** Space between rows. Unset: none for solid, outline and ghost; `'sm'` for separated and filled. */
241
+ gap?: AccordionGap;
242
+ /** Leading visual for every row: a node, or `(state) => node` (e.g. per row, or open vs closed). */
243
+ icon?: AccordionNode<Id>;
244
+ /** Trailing visual for every row, just before the indicator. Same shape as `icon`. */
245
+ endIcon?: AccordionNode<Id>;
246
+ /**
247
+ * Built-in glyph, your own node, or a function of the row's state.
248
+ * @default 'chevron'
249
+ */
250
+ indicator?: AccordionIndicator | AccordionNode<Id>;
251
+ /** @default 'end' */
252
+ indicatorPosition?: 'start' | 'end';
253
+ /**
254
+ * Rotate (or morph) the indicator when a row opens.
255
+ * @default true
256
+ */
257
+ animateIndicator?: boolean;
258
+ /**
259
+ * Heading level wrapping each trigger.
260
+ * @default 3
261
+ */
262
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
263
+ /**
264
+ * Animate open/close; `prefers-reduced-motion` always wins.
265
+ * @default true
266
+ */
267
+ animate?: boolean;
268
+ /**
269
+ * Lines between rows that touch.
270
+ * @default true
271
+ */
272
+ dividers?: boolean;
273
+ /** Text overflow per slot. Unset: title `wrap`, description `{ lines: 2 }`. */
274
+ overflow?: {
275
+ title?: AccordionOverflow;
276
+ description?: AccordionOverflow;
277
+ };
278
+ /** Explicit sizing, per dimension. Overrides the `size` preset. */
279
+ dimensions?: AccordionDimensions;
280
+ /**
281
+ * Drop every Pinfold class so no bundled CSS applies, keeping the behaviour,
282
+ * ARIA and `data-*` state hooks. For Tailwind or your own stylesheet:
283
+ * style entirely through `classNames`.
284
+ * @default false
285
+ */
286
+ unstyled?: boolean;
287
+ className?: string;
288
+ style?: React.CSSProperties;
289
+ }
290
+ /** Everything except the rows: shared by `AccordionConfig` and `AccordionMappedConfig`. */
291
+ interface AccordionOptions<Id extends string = string> extends AccordionBehaviourConfig<Id>, AccordionAppearanceConfig<Id>, SlotOverrides<AccordionSlot, AccordionItemState<Id>> {
292
+ /** Replace an entire row. The last resort before dropping to prop getters. */
293
+ renderItem?: (args: {
294
+ state: AccordionItemState<Id>;
295
+ /** Root props for the row. Spread them or state styling breaks. */
296
+ props: PropsBag;
297
+ /** The default header, if you only want to swap the body around it. */
298
+ header: React.ReactNode;
299
+ /** The default panel. */
300
+ panel: React.ReactNode;
301
+ }) => React.ReactNode;
302
+ }
303
+ /** Config with rows written as `AccordionItemConfig`. Ids are inferred from them. */
304
+ interface AccordionConfig<Id extends string = string> extends AccordionOptions<Id> {
305
+ /** The rows. The only required option. */
306
+ items: ReadonlyArray<AccordionItemConfig<Id>>;
307
+ /** Raw records need `AccordionMappedConfig` (a `fields` map). */
308
+ fields?: undefined;
309
+ }
310
+ /**
311
+ * Config for raw records (a CMS or database result): `fields` maps their keys onto rows, and
312
+ * every value must be a real key of the record, so a typo is a compile error.
313
+ *
314
+ * useAccordion({ items: faqRows, fields: { id: 'slug', title: 'question', content: 'answer' } });
315
+ */
316
+ interface AccordionMappedConfig<Row extends object = Record<string, unknown>> extends AccordionOptions<string> {
317
+ items: ReadonlyArray<Row>;
318
+ fields: AccordionFieldMap<Row>;
319
+ }
320
+ /** Either config shape. What the implementation and the generic APIs accept. */
321
+ type AnyAccordionConfig = AccordionConfig | AccordionMappedConfig;
322
+ /** Props accepted by `<Accordion />` itself, merged onto the root element. */
323
+ interface AccordionRootProps {
324
+ className?: string;
325
+ style?: React.CSSProperties;
326
+ children?: never;
327
+ [key: `data-${string}`]: unknown;
328
+ }
329
+ /** Addressing a single row from a sub-component. */
330
+ interface AccordionPartProps<Id extends string = string> {
331
+ id?: Id;
332
+ index?: number;
333
+ className?: string;
334
+ style?: React.CSSProperties;
335
+ children?: React.ReactNode;
336
+ }
337
+ interface AccordionState<Id extends string = string> {
338
+ items: AccordionItemState<Id>[];
339
+ value: Id[];
340
+ isOpen: (id: Id) => boolean;
341
+ open: (id: Id) => void;
342
+ close: (id: Id) => void;
343
+ toggle: (id: Id) => void;
344
+ openAll: () => void;
345
+ closeAll: () => void;
346
+ /** Move DOM focus to a header by index. */
347
+ focusItem: (index: number) => void;
348
+ /** Equivalent to `<Accordion />`, for callers who prefer a call. */
349
+ render: (props?: AccordionRootProps) => React.ReactElement;
350
+ getRootProps: (props?: PropsBag) => PropsBag;
351
+ getItemProps: (item: AccordionItemState<Id>, props?: PropsBag) => PropsBag;
352
+ getHeaderProps: (item: AccordionItemState<Id>, props?: PropsBag) => PropsBag;
353
+ getTriggerProps: (item: AccordionItemState<Id>, props?: PropsBag) => PropsBag;
354
+ getPanelProps: (item: AccordionItemState<Id>, props?: PropsBag) => PropsBag;
355
+ }
356
+ /** The sub-components for hand-laid-out accordions. */
357
+ interface AccordionParts<Id extends string = string> {
358
+ /** One complete row: header plus panel. */
359
+ Item: (props: AccordionPartProps<Id>) => React.ReactElement | null;
360
+ /** Just the heading and its button. `children` replaces the default label. */
361
+ Trigger: (props: AccordionPartProps<Id>) => React.ReactElement | null;
362
+ /** Just the collapsible region. `children` replaces the configured content. */
363
+ Panel: (props: AccordionPartProps<Id>) => React.ReactElement | null;
364
+ /** Just the open/closed glyph. */
365
+ Indicator: (props: AccordionPartProps<Id>) => React.ReactElement | null;
366
+ }
367
+ /**
368
+ * What `useAccordion()` returns: a component you render, that is also the
369
+ * controller, that also carries its parts.
370
+ */
371
+ type AccordionApi<Id extends string = string> = PinfoldComponent<AccordionState<Id> & AccordionParts<Id>, AccordionRootProps>;
372
+
373
+ export { usePinfoldComponent as y };
374
+ export type { AccordionConfig as A, PinfoldComponent as P, SlotOverrides as S, AccordionMappedConfig as a, AccordionApi as b, AnyAccordionConfig as c, AccordionRootProps as d, AccordionAppearanceConfig as e, AccordionBehaviourConfig as f, AccordionDimensions as g, AccordionGap as h, AccordionIndicator as i, AccordionItemConfig as j, AccordionItemId as k, AccordionItemState as l, AccordionNode as m, AccordionOptions as n, AccordionOverflow as o, AccordionPartProps as p, AccordionParts as q, AccordionRadius as r, AccordionSlot as s, AccordionState as t, AccordionValueDetails as u, AccordionVariant as v, SlotRenderer as w, Slottable as x };