@pinfold/react 0.1.0 → 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.
@@ -25,11 +25,14 @@
25
25
  "description": "A secondary line under the title."
26
26
  },
27
27
  "icon": {
28
- "description": "A leading visual."
28
+ "description": "A leading visual: a node, or a string (an emoji, or an icon-font ligature with `classNames.icon`). Overrides the accordion-wide `icon`."
29
29
  },
30
30
  "badge": {
31
31
  "description": "A trailing chip before the indicator."
32
32
  },
33
+ "endIcon": {
34
+ "description": "A trailing visual just before the indicator. Overrides the accordion-wide `endIcon`."
35
+ },
33
36
  "indicator": {
34
37
  "description": "Overrides the accordion-wide indicator for this row.",
35
38
  "anyOf": [
@@ -93,6 +96,10 @@
93
96
  "description": "The `badge` slot.",
94
97
  "type": "string"
95
98
  },
99
+ "endIcon": {
100
+ "description": "The `endIcon` slot.",
101
+ "type": "string"
102
+ },
96
103
  "indicator": {
97
104
  "description": "The `indicator` slot.",
98
105
  "type": "string"
@@ -148,6 +155,10 @@
148
155
  "description": "The `badge` slot.",
149
156
  "type": "object"
150
157
  },
158
+ "endIcon": {
159
+ "description": "The `endIcon` slot.",
160
+ "type": "object"
161
+ },
151
162
  "indicator": {
152
163
  "description": "The `indicator` slot.",
153
164
  "type": "object"
@@ -194,6 +205,10 @@
194
205
  "description": "Source field for `icon`.",
195
206
  "type": "string"
196
207
  },
208
+ "endIcon": {
209
+ "description": "Source field for `endIcon`.",
210
+ "type": "string"
211
+ },
197
212
  "badge": {
198
213
  "description": "Source field for `badge`.",
199
214
  "type": "string"
@@ -218,7 +233,7 @@
218
233
  ]
219
234
  },
220
235
  "collapsible": {
221
- "description": "In single mode, lets the user close the open row.",
236
+ "description": "Lets the user close the last open row. `false` keeps at least one row open, in either mode.",
222
237
  "default": true,
223
238
  "type": "boolean"
224
239
  },
@@ -241,6 +256,13 @@
241
256
  "default": false,
242
257
  "type": "boolean"
243
258
  },
259
+ "disabledIds": {
260
+ "description": "Disables these rows by id.",
261
+ "type": "array",
262
+ "items": {
263
+ "type": "string"
264
+ }
265
+ },
244
266
  "orientation": {
245
267
  "description": "Arrow-key axis between headers.",
246
268
  "default": "vertical",
@@ -332,16 +354,70 @@
332
354
  ]
333
355
  },
334
356
  "radius": {
335
- "description": "Corner radius preset.",
336
- "default": "md",
337
- "enum": [
338
- "none",
339
- "sm",
340
- "md",
341
- "lg",
342
- "full"
357
+ "description": "Corner radius of the container that holds the rows: a preset, or a length (`20` is px, `'1.25rem'` passes through). Default: the theme radius.",
358
+ "anyOf": [
359
+ {
360
+ "enum": [
361
+ "none",
362
+ "sm",
363
+ "md",
364
+ "lg",
365
+ "full"
366
+ ]
367
+ },
368
+ {
369
+ "type": [
370
+ "number",
371
+ "string"
372
+ ]
373
+ }
374
+ ]
375
+ },
376
+ "itemRadius": {
377
+ "description": "Corner radius of each row, same values as `radius`. Default: `radius` for separated, filled and ghost rows; square rows inside a solid or outline card.",
378
+ "anyOf": [
379
+ {
380
+ "enum": [
381
+ "none",
382
+ "sm",
383
+ "md",
384
+ "lg",
385
+ "full"
386
+ ]
387
+ },
388
+ {
389
+ "type": [
390
+ "number",
391
+ "string"
392
+ ]
393
+ }
394
+ ]
395
+ },
396
+ "gap": {
397
+ "description": "Space between rows: a preset, or a length (`12` is px). Default: none for solid, outline and ghost; `'sm'` for separated and filled.",
398
+ "anyOf": [
399
+ {
400
+ "enum": [
401
+ "none",
402
+ "sm",
403
+ "md",
404
+ "lg"
405
+ ]
406
+ },
407
+ {
408
+ "type": [
409
+ "number",
410
+ "string"
411
+ ]
412
+ }
343
413
  ]
344
414
  },
415
+ "icon": {
416
+ "description": "Leading visual for every row: a node, or `(state) => node`. A row’s own `icon` wins."
417
+ },
418
+ "endIcon": {
419
+ "description": "Trailing visual for every row, just before the indicator. A row’s own `endIcon` wins."
420
+ },
345
421
  "indicator": {
346
422
  "description": "A built-in glyph, your own node, or `(state) => node`.",
347
423
  "default": "chevron",
@@ -366,6 +442,11 @@
366
442
  "end"
367
443
  ]
368
444
  },
445
+ "animateIndicator": {
446
+ "description": "Rotate (or morph) the indicator when a row opens.",
447
+ "default": true,
448
+ "type": "boolean"
449
+ },
369
450
  "headingLevel": {
370
451
  "description": "Heading level around each trigger.",
371
452
  "default": 3,
@@ -384,7 +465,7 @@
384
465
  "type": "boolean"
385
466
  },
386
467
  "dividers": {
387
- "description": "Lines between rows.",
468
+ "description": "Lines between rows (solid, outline and ghost).",
388
469
  "default": true,
389
470
  "type": "boolean"
390
471
  },
@@ -459,7 +540,7 @@
459
540
  ]
460
541
  },
461
542
  "gap": {
462
- "description": "Space between rows.",
543
+ "description": "Space between rows. Overrides `gap`.",
463
544
  "type": [
464
545
  "number",
465
546
  "string"
@@ -543,7 +624,14 @@
543
624
  ]
544
625
  },
545
626
  "radius": {
546
- "description": "Corner radius as a length.",
627
+ "description": "Container corner radius. Overrides `radius`.",
628
+ "type": [
629
+ "number",
630
+ "string"
631
+ ]
632
+ },
633
+ "itemRadius": {
634
+ "description": "Row corner radius. Overrides `itemRadius`.",
547
635
  "type": [
548
636
  "number",
549
637
  "string"
@@ -612,6 +700,10 @@
612
700
  "description": "The `badge` slot.",
613
701
  "type": "string"
614
702
  },
703
+ "endIcon": {
704
+ "description": "The `endIcon` slot.",
705
+ "type": "string"
706
+ },
615
707
  "indicator": {
616
708
  "description": "The `indicator` slot.",
617
709
  "type": "string"
@@ -667,6 +759,10 @@
667
759
  "description": "The `badge` slot.",
668
760
  "type": "object"
669
761
  },
762
+ "endIcon": {
763
+ "description": "The `endIcon` slot.",
764
+ "type": "object"
765
+ },
670
766
  "indicator": {
671
767
  "description": "The `indicator` slot.",
672
768
  "type": "object"
@@ -722,6 +818,10 @@
722
818
  "description": "The `badge` slot.",
723
819
  "type": "object"
724
820
  },
821
+ "endIcon": {
822
+ "description": "The `endIcon` slot.",
823
+ "type": "object"
824
+ },
725
825
  "indicator": {
726
826
  "description": "The `indicator` slot.",
727
827
  "type": "object"
@@ -768,6 +868,9 @@
768
868
  "badge": {
769
869
  "description": "The `badge` slot."
770
870
  },
871
+ "endIcon": {
872
+ "description": "The `endIcon` slot."
873
+ },
771
874
  "indicator": {
772
875
  "description": "The `indicator` slot."
773
876
  },
@@ -1,278 +0,0 @@
1
- import * as React from 'react';
2
- import { Slottable as Slottable$1, PropsBag, AccordionFieldMap, AccordionChangeDetails, PinfoldOrientation, PinfoldDir, AccordionMountPanels, PinfoldLazy, PinfoldSize, PinfoldTone, PinfoldRadius, Length } 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
- /** A config field that is a node, or a function of live state returning one. */
38
- type Slottable<State, T = React.ReactNode> = Slottable$1<State, T>;
39
- /**
40
- * Per-slot escape hatches. Every Pinfold component exposes the same four, so
41
- * learning one component teaches all of them.
42
- *
43
- * - `classNames` — append a class to one slot.
44
- * - `slotProps` — merge arbitrary DOM props into one slot (event handlers chain).
45
- * - `styles` — inline style for one slot.
46
- * - `render` — replace one slot's element entirely, keeping its behaviour props.
47
- */
48
- interface SlotOverrides<Slot extends string, State = unknown> {
49
- classNames?: Partial<Record<Slot, string>>;
50
- styles?: Partial<Record<Slot, React.CSSProperties>>;
51
- slotProps?: Partial<Record<Slot, PropsBag>>;
52
- render?: Partial<Record<Slot, SlotRenderer<State>>>;
53
- }
54
- type SlotRenderer<State = unknown> = (args: {
55
- /** Behaviour props (ARIA, handlers, refs). Spread these or accessibility breaks. */
56
- props: PropsBag;
57
- /** Live component state for this slot. */
58
- state: State;
59
- children: React.ReactNode;
60
- }) => React.ReactNode;
61
-
62
- type AccordionSlot = 'root' | 'item' | 'header' | 'trigger' | 'icon' | 'label' | 'title' | 'description' | 'badge' | 'indicator' | 'panel' | 'content';
63
- type AccordionVariant = 'solid' | 'outline' | 'separated' | 'filled' | 'ghost';
64
- type AccordionIndicator = 'chevron' | 'plus' | 'caret' | 'arrow' | 'none';
65
- /** Runtime state of a single row. Passed to every function-valued config field. */
66
- interface AccordionItemState {
67
- id: string;
68
- index: number;
69
- open: boolean;
70
- disabled: boolean;
71
- config: AccordionItemConfig;
72
- triggerId: string;
73
- panelId: string;
74
- }
75
- /** Anything renderable in a row: a node, or a function of the row's live state. */
76
- type AccordionNode = Slottable<AccordionItemState>;
77
- /** One row of the accordion. This is the JSON a consumer — or a model — writes. */
78
- interface AccordionItemConfig {
79
- /** Stable key. Falls back to the array index when omitted. */
80
- id?: string;
81
- title: AccordionNode;
82
- content?: AccordionNode;
83
- /** Secondary line under the title. */
84
- description?: AccordionNode;
85
- /** Leading visual — icon, avatar, anything. */
86
- icon?: AccordionNode;
87
- /** Trailing chip rendered before the indicator. */
88
- badge?: AccordionNode;
89
- /** Overrides the accordion-wide indicator for this row only. */
90
- indicator?: AccordionIndicator | AccordionNode;
91
- disabled?: boolean;
92
- /** Open on first render (uncontrolled only). */
93
- defaultOpen?: boolean;
94
- /** Per-row class and style overrides, same slot names as the root config. */
95
- classNames?: Partial<Record<AccordionSlot, string>>;
96
- styles?: Partial<Record<AccordionSlot, React.CSSProperties>>;
97
- }
98
- /** Text overflow for one slot: wrap, one line with an ellipsis, or clamp to n lines. */
99
- type AccordionOverflow = 'wrap' | 'truncate' | {
100
- lines: number;
101
- };
102
- type AccordionValueDetails = AccordionChangeDetails<Event | React.SyntheticEvent>;
103
- interface AccordionBehaviourConfig {
104
- /**
105
- * The rows. The only required option. With `fields`, any record shape works:
106
- * `items: faqRows, fields: { id: 'slug', title: 'question', content: 'answer' }`.
107
- */
108
- items: AccordionItemConfig[] | ReadonlyArray<Record<string, any>>;
109
- /** Maps raw records onto rows, so CMS or database results need no `.map()`. */
110
- fields?: AccordionFieldMap;
111
- /** `single` closes siblings on open. Default `single`. */
112
- mode?: 'single' | 'multiple';
113
- /** In `single` mode, allow closing the open item. Default true. */
114
- collapsible?: boolean;
115
- /** Controlled open ids. */
116
- value?: string[];
117
- defaultValue?: string[];
118
- /** `details.reason`: 'click' | 'keyboard' | 'api' | 'find-in-page' | 'hash'. */
119
- onValueChange?: (value: string[], details: AccordionValueDetails) => void;
120
- /** Return `false` to block a change (e.g. unsaved edits in the open panel). */
121
- onBeforeChange?: (details: AccordionValueDetails) => boolean | void;
122
- /** Fires for one item, after `onValueChange`. */
123
- onItemToggle?: (id: string, open: boolean) => void;
124
- /** Arrow-key axis for moving between headers. Default `vertical`. */
125
- orientation?: PinfoldOrientation;
126
- dir?: PinfoldDir;
127
- /** Wrap focus at the ends. Default true. */
128
- loop?: boolean;
129
- /** Prefix for generated ids — set it for deterministic SSR markup. */
130
- id?: string;
131
- /** Disables every row. Rows stay focusable. Default false. */
132
- disabled?: boolean;
133
- /**
134
- * Closed panels use `hidden="until-found"`, so the browser's find-in-page
135
- * searches them and opens the match. Default true.
136
- */
137
- hiddenUntilFound?: boolean;
138
- /**
139
- * When panel content is in the DOM. `always` (default) keeps it searchable
140
- * and indexable; `open` mounts only open panels; `first-open` mounts on
141
- * first open and keeps it.
142
- */
143
- mountPanels?: AccordionMountPanels;
144
- /** @deprecated Use `mountPanels: 'open'`. */
145
- unmountClosed?: boolean;
146
- /** `#id` in the URL opens that row and scrolls to it; opening a row updates the hash. Default false. */
147
- syncWithHash?: boolean;
148
- /** Code loading for the `/lazy` entry. The accordion is light, so `auto` loads eagerly. */
149
- lazy?: PinfoldLazy;
150
- }
151
- /**
152
- * Explicit sizing. Numbers are px, strings pass through (`'50%'`, `'18rem'`,
153
- * `'clamp(20rem, 40vw, 32rem)'`). Each key overrides the value the `size`
154
- * preset would have chosen, so you can take one dimension and leave the rest.
155
- */
156
- interface AccordionDimensions {
157
- width?: Length;
158
- minWidth?: Length;
159
- maxWidth?: Length;
160
- height?: Length;
161
- maxHeight?: Length;
162
- /** Space between rows (outline / separated / filled variants). */
163
- gap?: Length;
164
- /** Trigger padding. */
165
- paddingX?: Length;
166
- paddingY?: Length;
167
- /** Panel padding. Defaults to the trigger padding. */
168
- panelPaddingX?: Length;
169
- panelPaddingY?: Length;
170
- /** Cap the open panel and let it scroll past that. */
171
- panelMaxHeight?: Length;
172
- fontSize?: Length;
173
- titleSize?: Length;
174
- descriptionSize?: Length;
175
- iconSize?: Length;
176
- indicatorSize?: Length;
177
- borderWidth?: Length;
178
- /** Corner radius as a length — overrides the `radius` token preset. */
179
- radius?: Length;
180
- /** Open/close transition duration, e.g. `'320ms'`. */
181
- duration?: Length;
182
- }
183
- interface AccordionAppearanceConfig {
184
- variant?: AccordionVariant;
185
- size?: PinfoldSize;
186
- tone?: PinfoldTone;
187
- radius?: PinfoldRadius;
188
- /** Built-in glyph, your own node, or a function of the row's state. */
189
- indicator?: AccordionIndicator | AccordionNode;
190
- /** Which edge the indicator sits on. Default `end`. */
191
- indicatorPosition?: 'start' | 'end';
192
- /** Heading level wrapping each trigger. Default 3. */
193
- headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
194
- /** Animate open/close. Default true; honours prefers-reduced-motion regardless. */
195
- animate?: boolean;
196
- /** Full-width dividers between rows (solid/ghost variants). Default true. */
197
- dividers?: boolean;
198
- /** Text overflow per slot. Default: title `wrap`, description `{ lines: 2 }`. */
199
- overflow?: {
200
- title?: AccordionOverflow;
201
- description?: AccordionOverflow;
202
- };
203
- /** Explicit sizing, per dimension. Overrides the `size` preset. */
204
- dimensions?: AccordionDimensions;
205
- /**
206
- * Drop every Pinfold class so no bundled CSS applies, keeping the behaviour,
207
- * ARIA and `data-*` state hooks. For Tailwind or your own stylesheet:
208
- * style entirely through `classNames`.
209
- */
210
- unstyled?: boolean;
211
- className?: string;
212
- style?: React.CSSProperties;
213
- }
214
- interface AccordionConfig extends AccordionBehaviourConfig, AccordionAppearanceConfig, SlotOverrides<AccordionSlot, AccordionItemState> {
215
- /** Replace an entire row. The last resort before dropping to prop getters. */
216
- renderItem?: (args: {
217
- state: AccordionItemState;
218
- /** Root props for the row. Spread them or state styling breaks. */
219
- props: PropsBag;
220
- /** The default header, if you only want to swap the body around it. */
221
- header: React.ReactNode;
222
- /** The default panel. */
223
- panel: React.ReactNode;
224
- }) => React.ReactNode;
225
- }
226
- /** Props accepted by `<Accordion />` itself, merged onto the root element. */
227
- interface AccordionRootProps {
228
- className?: string;
229
- style?: React.CSSProperties;
230
- children?: never;
231
- [key: `data-${string}`]: unknown;
232
- }
233
- /** Addressing a single row from a sub-component. */
234
- interface AccordionPartProps {
235
- id?: string;
236
- index?: number;
237
- className?: string;
238
- style?: React.CSSProperties;
239
- children?: React.ReactNode;
240
- }
241
- interface AccordionState {
242
- items: AccordionItemState[];
243
- value: string[];
244
- isOpen: (id: string) => boolean;
245
- open: (id: string) => void;
246
- close: (id: string) => void;
247
- toggle: (id: string) => void;
248
- openAll: () => void;
249
- closeAll: () => void;
250
- /** Move DOM focus to a header by index. */
251
- focusItem: (index: number) => void;
252
- /** Equivalent to `<Accordion />`, for callers who prefer a call. */
253
- render: (props?: AccordionRootProps) => React.ReactElement;
254
- getRootProps: (props?: PropsBag) => PropsBag;
255
- getItemProps: (item: AccordionItemState, props?: PropsBag) => PropsBag;
256
- getHeaderProps: (item: AccordionItemState, props?: PropsBag) => PropsBag;
257
- getTriggerProps: (item: AccordionItemState, props?: PropsBag) => PropsBag;
258
- getPanelProps: (item: AccordionItemState, props?: PropsBag) => PropsBag;
259
- }
260
- /** The sub-components for hand-laid-out accordions. */
261
- interface AccordionParts {
262
- /** One complete row: header plus panel. */
263
- Item: (props: AccordionPartProps) => React.ReactElement | null;
264
- /** Just the heading and its button. `children` replaces the default label. */
265
- Trigger: (props: AccordionPartProps) => React.ReactElement | null;
266
- /** Just the collapsible region. `children` replaces the configured content. */
267
- Panel: (props: AccordionPartProps) => React.ReactElement | null;
268
- /** Just the open/closed glyph. */
269
- Indicator: (props: AccordionPartProps) => React.ReactElement | null;
270
- }
271
- /**
272
- * What `useAccordion()` returns: a component you render, that is also the
273
- * controller, that also carries its parts.
274
- */
275
- type AccordionApi = PinfoldComponent<AccordionState & AccordionParts, AccordionRootProps>;
276
-
277
- export { usePinfoldComponent as u };
278
- export type { AccordionConfig as A, PinfoldComponent as P, SlotOverrides as S, AccordionApi as a, AccordionRootProps as b, AccordionAppearanceConfig as c, AccordionBehaviourConfig as d, AccordionDimensions as e, AccordionIndicator as f, AccordionItemConfig as g, AccordionItemState as h, AccordionNode as i, AccordionOverflow as j, AccordionPartProps as k, AccordionParts as l, AccordionSlot as m, AccordionState as n, AccordionValueDetails as o, AccordionVariant as p, SlotRenderer as q, Slottable as r };