@central-ui/react 0.2.0 → 0.3.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.
@@ -337,12 +337,16 @@ type MessageProps = Omit<ComponentProps<'div'>, 'children'> & StyleProps & {
337
337
  export declare const Error: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
338
338
  /** Shows itself only while Root's validation is `'success'`: with no children it shows the message from `['success', message]`, a function children formats that message, and other children replace it. */
339
339
  export declare const Success: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
340
- /** Built-in decorative open-button icon. Place inside AddonOpenButton; no children needed. Defaults to strokeWidth 1.5. */
341
- export declare const AddonOpenButtonIcon: import("react").ForwardRefExoticComponent<Omit<SVGIconProps & StyleProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
342
- /** Built-in decorative clear icon. Place inside AddonClearButton; no children needed. Defaults to strokeWidth 1.5. */
343
- export declare const AddonClearButtonIcon: import("react").ForwardRefExoticComponent<Omit<SVGIconProps & StyleProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
340
+ type BuiltInIconProps = Omit<SVGIconProps, 'children'> & StyleProps & {
341
+ /** Omit for the supplied default icon; pass a child icon only to replace it. */
342
+ children?: ReactElement<SVGIconProps>;
343
+ };
344
+ /** Built-in decorative open-button icon. Place inside AddonOpenButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
345
+ export declare const AddonOpenButtonIcon: import("react").ForwardRefExoticComponent<Omit<BuiltInIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
346
+ /** Built-in decorative clear icon. Place inside AddonClearButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
347
+ export declare const AddonClearButtonIcon: import("react").ForwardRefExoticComponent<Omit<BuiltInIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
344
348
  /** Optional spinner inside Addon. Does not detect requests: you decide when to render it. Defaults to strokeWidth 1.5. */
345
- export declare const AddonLoadingIcon: import("react").ForwardRefExoticComponent<Omit<SVGIconProps & StyleProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
349
+ export declare const AddonLoadingIcon: import("react").ForwardRefExoticComponent<Omit<BuiltInIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
346
350
  /** Optional async status inside Popup, below Empty and above ScrollArea. Omit entirely for synchronous items. For async items, keep Loading mounted and conditionally render only LoadingIcon and LoadingText from your request state. Does not detect requests or manage visibility. */
347
351
  export declare const Loading: import("react").ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react").AutocompleteStatusProps, "className"> & {
348
352
  className?: string;
@@ -326,11 +326,11 @@ const Message = /* @__PURE__ */ forwardRef(function Message({ disableStyle, clas
326
326
  const attributes = usePartAttributes();
327
327
  return _jsx("div", { "aria-atomic": "true", ...attributes, ...props, id: describedById, ref: ref, "data-component": component, "data-style-disabled": disableStyle ? '' : undefined, className: className });
328
328
  });
329
- /** Built-in decorative open-button icon. Place inside AddonOpenButton; no children needed. Defaults to strokeWidth 1.5. */
329
+ /** Built-in decorative open-button icon. Place inside AddonOpenButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
330
330
  export const AddonOpenButtonIcon = /* @__PURE__ */ forwardRef(function AddonOpenButtonIcon(props, ref) {
331
331
  return _jsx(BuiltInIcon, { ...props, ref: ref, icon: ChevronsUpDown, component: "autocomplete-addonopenbuttonicon" });
332
332
  });
333
- /** Built-in decorative clear icon. Place inside AddonClearButton; no children needed. Defaults to strokeWidth 1.5. */
333
+ /** Built-in decorative clear icon. Place inside AddonClearButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
334
334
  export const AddonClearButtonIcon = /* @__PURE__ */ forwardRef(function AddonClearButtonIcon(props, ref) {
335
335
  return _jsx(BuiltInIcon, { ...props, ref: ref, icon: X, component: "autocomplete-addonclearbuttonicon" });
336
336
  });
@@ -339,9 +339,18 @@ export const AddonLoadingIcon = /* @__PURE__ */ forwardRef(function AddonLoading
339
339
  return _jsx(BuiltInIcon, { ...props, ref: ref, icon: LoaderCircle, component: "autocomplete-addonloadingicon" });
340
340
  });
341
341
  // The shared body of the built-in decorative icons above, which only differ by their icon and component name
342
- const BuiltInIcon = /* @__PURE__ */ forwardRef(function BuiltInIcon({ disableStyle, className, strokeWidth = 1.5, icon: Icon, component, ...props }, ref) {
342
+ const BuiltInIcon = /* @__PURE__ */ forwardRef(function BuiltInIcon({ disableStyle, className, strokeWidth, icon: Icon, component, children, ...props }, ref) {
343
343
  const attributes = usePartAttributes();
344
- return _jsx(Icon, { ...attributes, "aria-hidden": true, ...props, ref: ref, strokeWidth: strokeWidth, "data-component": component, "data-style-disabled": disableStyle ? '' : undefined, className: className });
344
+ return cloneElement(children ?? _jsx(Icon, {}), {
345
+ ...attributes,
346
+ 'aria-hidden': true,
347
+ ...props,
348
+ ref,
349
+ strokeWidth: strokeWidth ?? children?.props.strokeWidth ?? 1.5,
350
+ 'data-component': component,
351
+ 'data-style-disabled': disableStyle ? '' : undefined,
352
+ className: twMerge(children?.props.className, className),
353
+ });
345
354
  });
346
355
  /** Optional async status inside Popup, below Empty and above ScrollArea. Omit entirely for synchronous items. For async items, keep Loading mounted and conditionally render only LoadingIcon and LoadingText from your request state. Does not detect requests or manage visibility. */
347
356
  export const Loading = /* @__PURE__ */ forwardRef(function Loading({ disableStyle, className, ...props }, ref) {
@@ -0,0 +1,169 @@
1
+ import type { StyleProps } from '../../types/Style.ts';
2
+ import type { SVGIconProps } from '../../types/Icon.ts';
3
+ import type { AsChildProps } from '../../utils/asChild.ts';
4
+ import type { AutoplayOptionsType } from 'embla-carousel-autoplay';
5
+ import type { ComponentProps, ReactElement, ReactNode } from 'react';
6
+ type IconProps = Omit<SVGIconProps, 'strokeWidth'> & {
7
+ /** @default 1.5 */
8
+ strokeWidth?: SVGIconProps['strokeWidth'];
9
+ };
10
+ type Orientation = 'horizontal' | 'vertical';
11
+ export type RootProps = {
12
+ /** The carousel parts, like SlidesList, the buttons and IndicatorList. */
13
+ children: ReactNode;
14
+ /**
15
+ * The direction slides move in. A vertical carousel needs a height on SlidesList.
16
+ * @default 'horizontal'
17
+ */
18
+ orientation?: Orientation;
19
+ /**
20
+ * Whether the last slide leads back to the first, and the first back to the last.
21
+ * @default false
22
+ */
23
+ loop?: boolean;
24
+ /**
25
+ * Where the selected slide sits inside SlidesList, when slides are narrower than it.
26
+ * @default 'center'
27
+ */
28
+ align?: 'start' | 'center' | 'end';
29
+ /**
30
+ * How many slides one Previous/Next press moves. 'auto' moves by as many slides as are fully visible.
31
+ * @default 1
32
+ */
33
+ slidesToScroll?: number | 'auto';
34
+ /**
35
+ * Whether a drag glides freely with momentum instead of stopping on a slide.
36
+ * @default false
37
+ */
38
+ dragFree?: boolean;
39
+ /**
40
+ * Space between slides, as a number of pixels or any CSS length. Size slides with a className on Slide, like `basis-1/3`; the gap is already subtracted from it.
41
+ * @default 0
42
+ */
43
+ gap?: number | string;
44
+ /**
45
+ * Turns off dragging and every button.
46
+ * @default false
47
+ */
48
+ disabled?: boolean;
49
+ /**
50
+ * Moves to the next slide on its own. Pass true for the defaults, or an object to change them:
51
+ * - delay: milliseconds between slides (default 4000)
52
+ * - jump: jump instead of animating (default false)
53
+ * - playOnInit: start playing as soon as the carousel mounts (default true)
54
+ * - stopOnInteraction: whether a drag, hover or focus stops it for good; set false to resume once the user lets go, moves the mouse away, or moves focus out (default true)
55
+ * - stopOnMouseEnter: stop while the mouse is over SlidesList; it resumes on mouse leave only with stopOnInteraction false (default false)
56
+ * - stopOnFocusIn: stop when focus moves inside SlidesList; it resumes on focus out only with stopOnInteraction false (default true)
57
+ * - stopOnLastSnap: stop on the last slide (default false)
58
+ * @default false
59
+ */
60
+ autoplay?: boolean | Omit<AutoplayOptionsType, 'active' | 'breakpoints' | 'rootNode'>;
61
+ /** The selected slide's position, from 0. Use with onIndexChange to control it yourself. */
62
+ index?: number;
63
+ /**
64
+ * The slide the carousel starts on, when `index` isn't controlled.
65
+ * @default 0
66
+ */
67
+ defaultIndex?: number;
68
+ /** Called with the new position whenever another slide gets selected, by a drag, a button, or autoplay. */
69
+ onIndexChange?: (index: number) => void;
70
+ };
71
+ /**
72
+ * A row of slides users swipe through, built on Embla Carousel. Root holds the carousel's state and renders no element;
73
+ * SlidesList is the visible window and Slide is one item, written by hand so each can hold different content.
74
+ * Previous/Next and Indicator buttons work on their own: no click handlers needed.
75
+ * Every part carries data-orientation, data-disabled, and data-playing (while autoplay runs), so any part can be styled by the carousel's state.
76
+ *
77
+ * @example Complete composition
78
+ * import { Carousel } from '@central-ui/react'
79
+ *
80
+ * function Example() {
81
+ * return (
82
+ * <Carousel.Root loop>
83
+ * <Carousel.SlidesList>
84
+ * {products.map((product) => (
85
+ * <Carousel.Slide key={product.id}>
86
+ * <img src={product.image} alt={product.title} />
87
+ * </Carousel.Slide>
88
+ * ))}
89
+ * </Carousel.SlidesList>
90
+ * <Carousel.PreviousButton aria-label="Previous slide">
91
+ * <Carousel.PreviousButtonIcon />
92
+ * </Carousel.PreviousButton>
93
+ * <Carousel.NextButton aria-label="Next slide">
94
+ * <Carousel.NextButtonIcon />
95
+ * </Carousel.NextButton>
96
+ * <Carousel.IndicatorList>
97
+ * {(index) => <Carousel.IndicatorButton index={index} />}
98
+ * </Carousel.IndicatorList>
99
+ * </Carousel.Root>
100
+ * )
101
+ * }
102
+ *
103
+ * @example Several slides at once
104
+ * // Size slides with Tailwind (responsive classes work); gap is subtracted from that size.
105
+ * ;<Carousel.Root gap={12} align="start" slidesToScroll="auto">
106
+ * <Carousel.SlidesList>
107
+ * <Carousel.Slide className="basis-full md:basis-1/3">…</Carousel.Slide>
108
+ * </Carousel.SlidesList>
109
+ * </Carousel.Root>
110
+ *
111
+ * @example Autoplay, pausing while hovered and resuming on mouse leave
112
+ * ;<Carousel.Root loop autoplay={{ delay: 5000, stopOnMouseEnter: true, stopOnInteraction: false }}>
113
+ *
114
+ * @example Controlled position
115
+ * const [index, setIndex] = useState(0)
116
+ * ;<Carousel.Root index={index} onIndexChange={setIndex}>
117
+ */
118
+ export declare function Root({ orientation, loop, align, slidesToScroll, dragFree, gap, disabled, autoplay, index, defaultIndex, onIndexChange, children, }: RootProps): import("react").JSX.Element;
119
+ /** The visible window the slides move inside, announced to screen readers as the carousel. Style it freely (background, border, radius); a vertical carousel needs a height here. Put only Slides inside. */
120
+ export declare const SlidesList: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & StyleProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
121
+ /**
122
+ * One slide; each can hold different content. Size it with a className, like `basis-1/3`; it fills SlidesList by default.
123
+ * Its padding makes the gap between slides, so put backgrounds, borders and radius on its content, not on the Slide itself.
124
+ */
125
+ export declare const Slide: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & StyleProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
126
+ type ButtonPartProps = ComponentProps<'button'> & AsChildProps & StyleProps;
127
+ /** Moves to the previous slide; no click handler needed. Disabled when there's no slide before (never with loop). Supply its content, normally PreviousButtonIcon, and an accessible label. Pass asChild to give this behavior to your own button instead. */
128
+ export declare const PreviousButton: import("react").ForwardRefExoticComponent<Omit<ButtonPartProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
129
+ /** Moves to the next slide; no click handler needed. Disabled when there's no slide after (never with loop). Supply its content, normally NextButtonIcon, and an accessible label. Pass asChild to give this behavior to your own button instead. */
130
+ export declare const NextButton: import("react").ForwardRefExoticComponent<Omit<ButtonPartProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
131
+ /**
132
+ * Renders one IndicatorButton per position through its children function, which receives that position's index.
133
+ * The count comes from the carousel itself: with several slides per view, it counts positions, not slides.
134
+ *
135
+ * @example Dots
136
+ * ;<Carousel.IndicatorList>
137
+ * {(index) => <Carousel.IndicatorButton index={index} />}
138
+ * </Carousel.IndicatorList>
139
+ */
140
+ export declare const IndicatorList: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "children"> & StyleProps & {
141
+ /** Called once per position with its index; return an IndicatorButton. */
142
+ children: (index: number) => ReactNode;
143
+ }, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
144
+ /**
145
+ * Moves to its position when clicked, and carries data-selected while that position is shown. Shows as a dot when empty.
146
+ * Pass asChild to give this behavior to your own button instead.
147
+ *
148
+ * @example Thumbnails instead of dots
149
+ * ;<Carousel.IndicatorList>
150
+ * {(index) => (
151
+ * <Carousel.IndicatorButton index={index}>
152
+ * <img src={images[index].thumbnail} alt="" />
153
+ * </Carousel.IndicatorButton>
154
+ * )}
155
+ * </Carousel.IndicatorList>
156
+ */
157
+ export declare const IndicatorButton: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & AsChildProps & StyleProps & {
158
+ /** The position this button moves to, from IndicatorList's children function. */
159
+ index: number;
160
+ }, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
161
+ type BuiltInIconProps = Omit<IconProps, 'children'> & StyleProps & {
162
+ /** Omit for the supplied default icon; pass a child icon only to replace it. */
163
+ children?: ReactElement<SVGIconProps>;
164
+ };
165
+ /** Built-in decorative previous icon. Place inside PreviousButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
166
+ export declare const PreviousButtonIcon: import("react").ForwardRefExoticComponent<Omit<BuiltInIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
167
+ /** Built-in decorative next icon. Place inside NextButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
168
+ export declare const NextButtonIcon: import("react").ForwardRefExoticComponent<Omit<BuiltInIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
169
+ export {};
@@ -0,0 +1,299 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { asChildStyleAttributes } from "../../utils/asChild.js";
3
+ import { ChevronLeft, ChevronRight } from "../../utils/icons.js";
4
+ import { useDirection } from '@base-ui/react/direction-provider';
5
+ import { mergeProps } from '@base-ui/react/merge-props';
6
+ import { useRender } from '@base-ui/react/use-render';
7
+ import useEmblaCarousel from 'embla-carousel-react';
8
+ import Autoplay from 'embla-carousel-autoplay';
9
+ import { Fragment, cloneElement, createContext, forwardRef, useContext, useEffect, useState } from 'react';
10
+ import { twMerge } from 'tailwind-merge';
11
+ const CarouselContext = /* @__PURE__ */ createContext(null);
12
+ /**
13
+ * A row of slides users swipe through, built on Embla Carousel. Root holds the carousel's state and renders no element;
14
+ * SlidesList is the visible window and Slide is one item, written by hand so each can hold different content.
15
+ * Previous/Next and Indicator buttons work on their own: no click handlers needed.
16
+ * Every part carries data-orientation, data-disabled, and data-playing (while autoplay runs), so any part can be styled by the carousel's state.
17
+ *
18
+ * @example Complete composition
19
+ * import { Carousel } from '@central-ui/react'
20
+ *
21
+ * function Example() {
22
+ * return (
23
+ * <Carousel.Root loop>
24
+ * <Carousel.SlidesList>
25
+ * {products.map((product) => (
26
+ * <Carousel.Slide key={product.id}>
27
+ * <img src={product.image} alt={product.title} />
28
+ * </Carousel.Slide>
29
+ * ))}
30
+ * </Carousel.SlidesList>
31
+ * <Carousel.PreviousButton aria-label="Previous slide">
32
+ * <Carousel.PreviousButtonIcon />
33
+ * </Carousel.PreviousButton>
34
+ * <Carousel.NextButton aria-label="Next slide">
35
+ * <Carousel.NextButtonIcon />
36
+ * </Carousel.NextButton>
37
+ * <Carousel.IndicatorList>
38
+ * {(index) => <Carousel.IndicatorButton index={index} />}
39
+ * </Carousel.IndicatorList>
40
+ * </Carousel.Root>
41
+ * )
42
+ * }
43
+ *
44
+ * @example Several slides at once
45
+ * // Size slides with Tailwind (responsive classes work); gap is subtracted from that size.
46
+ * ;<Carousel.Root gap={12} align="start" slidesToScroll="auto">
47
+ * <Carousel.SlidesList>
48
+ * <Carousel.Slide className="basis-full md:basis-1/3">…</Carousel.Slide>
49
+ * </Carousel.SlidesList>
50
+ * </Carousel.Root>
51
+ *
52
+ * @example Autoplay, pausing while hovered and resuming on mouse leave
53
+ * ;<Carousel.Root loop autoplay={{ delay: 5000, stopOnMouseEnter: true, stopOnInteraction: false }}>
54
+ *
55
+ * @example Controlled position
56
+ * const [index, setIndex] = useState(0)
57
+ * ;<Carousel.Root index={index} onIndexChange={setIndex}>
58
+ */
59
+ export function Root({ orientation = 'horizontal', loop = false, align = 'center', slidesToScroll = 1, dragFree = false, gap = 0, disabled = false, autoplay = false, index, defaultIndex = 0, onIndexChange, children, }) {
60
+ const direction = useDirection();
61
+ // Embla reads the start only once; keeping it fixed stops a controlled index from restarting the carousel
62
+ const [startIndex] = useState(index ?? defaultIndex);
63
+ const [emblaRef, api] = useEmblaCarousel({
64
+ axis: orientation === 'vertical' ? 'y' : 'x',
65
+ direction,
66
+ loop,
67
+ align,
68
+ slidesToScroll,
69
+ dragFree,
70
+ startIndex,
71
+ active: !disabled,
72
+ }, autoplay ? [Autoplay(autoplay === true ? {} : autoplay)] : []);
73
+ const [snapshot, setSnapshot] = useState({
74
+ selectedIndex: startIndex,
75
+ snapCount: 0,
76
+ canScrollPrevious: false,
77
+ canScrollNext: false,
78
+ playing: false,
79
+ });
80
+ //1. Mirroring Embla's state, so the parts re-render when it changes
81
+ useEffect(() => {
82
+ if (!api)
83
+ return;
84
+ function sync(carousel, event) {
85
+ setSnapshot({
86
+ selectedIndex: carousel.selectedScrollSnap(),
87
+ snapCount: carousel.scrollSnapList().length,
88
+ canScrollPrevious: carousel.canScrollPrev(),
89
+ canScrollNext: carousel.canScrollNext(),
90
+ playing: readPlaying(carousel, event),
91
+ });
92
+ }
93
+ // The plugin fires play/stop before updating isPlaying(), so those events decide it themselves
94
+ function readPlaying(carousel, event) {
95
+ if (event === 'autoplay:play')
96
+ return true;
97
+ if (event === 'autoplay:stop')
98
+ return false;
99
+ // Embla starts no plugins while disabled, so plugins() is undefined then
100
+ return carousel.plugins()?.autoplay?.isPlaying() ?? false;
101
+ }
102
+ sync(api);
103
+ api.on('select', sync).on('reInit', sync).on('autoplay:play', sync).on('autoplay:stop', sync);
104
+ return () => {
105
+ api.off('select', sync).off('reInit', sync).off('autoplay:play', sync).off('autoplay:stop', sync);
106
+ };
107
+ }, [api]);
108
+ //2. Reporting every newly selected slide
109
+ useEffect(() => {
110
+ if (!api || !onIndexChange)
111
+ return;
112
+ function handleSelect(carousel) {
113
+ onIndexChange?.(carousel.selectedScrollSnap());
114
+ }
115
+ api.on('select', handleSelect);
116
+ return () => {
117
+ api.off('select', handleSelect);
118
+ };
119
+ }, [api, onIndexChange]);
120
+ //3. Moving to a controlled index set from outside
121
+ useEffect(() => {
122
+ if (!api || index === undefined || index === api.selectedScrollSnap())
123
+ return;
124
+ api.scrollTo(index);
125
+ }, [api, index]);
126
+ const context = { emblaRef, api, orientation, disabled, gap, ...snapshot };
127
+ return _jsx(CarouselContext.Provider, { value: context, children: children });
128
+ }
129
+ /** The visible window the slides move inside, announced to screen readers as the carousel. Style it freely (background, border, radius); a vertical carousel needs a height here. Put only Slides inside. */
130
+ export const SlidesList = /* @__PURE__ */ forwardRef(function SlidesList({ disableStyle, style, children, ...props }, ref) {
131
+ const context = useCarouselContext('SlidesList');
132
+ const vertical = context.orientation === 'vertical';
133
+ // The track Embla moves; its negative margin cancels the first slide's gap
134
+ const track = (_jsx("div", { style: {
135
+ display: 'flex',
136
+ flexDirection: vertical ? 'column' : 'row',
137
+ height: vertical ? '100%' : undefined,
138
+ touchAction: vertical ? 'pan-x pinch-zoom' : 'pan-y pinch-zoom',
139
+ [vertical ? 'marginBlockStart' : 'marginInlineStart']: negativeLength(context.gap),
140
+ }, children: children }));
141
+ return useRender({
142
+ ref: [ref, context.emblaRef],
143
+ state: partState(context),
144
+ props: {
145
+ role: 'region',
146
+ 'aria-roledescription': 'carousel',
147
+ ...props,
148
+ style: { overflow: 'hidden', ...style },
149
+ 'data-component': 'carousel-slideslist',
150
+ 'data-style-disabled': disableStyle ? '' : undefined,
151
+ children: track,
152
+ },
153
+ });
154
+ });
155
+ /**
156
+ * One slide; each can hold different content. Size it with a className, like `basis-1/3`; it fills SlidesList by default.
157
+ * Its padding makes the gap between slides, so put backgrounds, borders and radius on its content, not on the Slide itself.
158
+ */
159
+ export const Slide = /* @__PURE__ */ forwardRef(function Slide({ disableStyle, style, ...props }, ref) {
160
+ const context = useCarouselContext('Slide');
161
+ const vertical = context.orientation === 'vertical';
162
+ return useRender({
163
+ ref,
164
+ state: partState(context),
165
+ props: {
166
+ role: 'group',
167
+ 'aria-roledescription': 'slide',
168
+ ...props,
169
+ style: {
170
+ flexShrink: 0,
171
+ minWidth: 0,
172
+ minHeight: 0,
173
+ boxSizing: 'border-box',
174
+ [vertical ? 'paddingBlockStart' : 'paddingInlineStart']: context.gap,
175
+ ...style,
176
+ },
177
+ 'data-component': 'carousel-slide',
178
+ 'data-style-disabled': disableStyle ? '' : undefined,
179
+ },
180
+ });
181
+ });
182
+ /** Moves to the previous slide; no click handler needed. Disabled when there's no slide before (never with loop). Supply its content, normally PreviousButtonIcon, and an accessible label. Pass asChild to give this behavior to your own button instead. */
183
+ export const PreviousButton = /* @__PURE__ */ forwardRef(function PreviousButton(props, ref) {
184
+ const context = useCarouselContext('PreviousButton');
185
+ return useActionButton(props, ref, context, {
186
+ component: 'carousel-previousbutton',
187
+ disabled: context.disabled || !context.canScrollPrevious,
188
+ onClick: () => context.api?.scrollPrev(),
189
+ });
190
+ });
191
+ /** Moves to the next slide; no click handler needed. Disabled when there's no slide after (never with loop). Supply its content, normally NextButtonIcon, and an accessible label. Pass asChild to give this behavior to your own button instead. */
192
+ export const NextButton = /* @__PURE__ */ forwardRef(function NextButton(props, ref) {
193
+ const context = useCarouselContext('NextButton');
194
+ return useActionButton(props, ref, context, {
195
+ component: 'carousel-nextbutton',
196
+ disabled: context.disabled || !context.canScrollNext,
197
+ onClick: () => context.api?.scrollNext(),
198
+ });
199
+ });
200
+ /**
201
+ * Renders one IndicatorButton per position through its children function, which receives that position's index.
202
+ * The count comes from the carousel itself: with several slides per view, it counts positions, not slides.
203
+ *
204
+ * @example Dots
205
+ * ;<Carousel.IndicatorList>
206
+ * {(index) => <Carousel.IndicatorButton index={index} />}
207
+ * </Carousel.IndicatorList>
208
+ */
209
+ export const IndicatorList = /* @__PURE__ */ forwardRef(function IndicatorList({ disableStyle, children, ...props }, ref) {
210
+ const context = useCarouselContext('IndicatorList');
211
+ return useRender({
212
+ ref,
213
+ state: partState(context),
214
+ props: {
215
+ ...props,
216
+ 'data-component': 'carousel-indicatorlist',
217
+ 'data-style-disabled': disableStyle ? '' : undefined,
218
+ children: Array.from({ length: context.snapCount }, (_, index) => _jsx(Fragment, { children: children(index) }, index)),
219
+ },
220
+ });
221
+ });
222
+ /**
223
+ * Moves to its position when clicked, and carries data-selected while that position is shown. Shows as a dot when empty.
224
+ * Pass asChild to give this behavior to your own button instead.
225
+ *
226
+ * @example Thumbnails instead of dots
227
+ * ;<Carousel.IndicatorList>
228
+ * {(index) => (
229
+ * <Carousel.IndicatorButton index={index}>
230
+ * <img src={images[index].thumbnail} alt="" />
231
+ * </Carousel.IndicatorButton>
232
+ * )}
233
+ * </Carousel.IndicatorList>
234
+ */
235
+ export const IndicatorButton = /* @__PURE__ */ forwardRef(function IndicatorButton({ index, ...props }, ref) {
236
+ const context = useCarouselContext('IndicatorButton');
237
+ const selected = context.selectedIndex === index;
238
+ return useActionButton(props, ref, context, {
239
+ component: 'carousel-indicatorbutton',
240
+ disabled: context.disabled,
241
+ selected,
242
+ attributes: { 'aria-label': `Go to slide ${index + 1}`, 'aria-current': selected ? 'true' : undefined },
243
+ onClick: () => context.api?.scrollTo(index),
244
+ });
245
+ });
246
+ /** Built-in decorative previous icon. Place inside PreviousButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
247
+ export const PreviousButtonIcon = /* @__PURE__ */ forwardRef(function PreviousButtonIcon(props, ref) {
248
+ return _jsx(BuiltInIcon, { ...props, ref: ref, icon: ChevronLeft, component: "carousel-previousbuttonicon" });
249
+ });
250
+ /** Built-in decorative next icon. Place inside NextButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
251
+ export const NextButtonIcon = /* @__PURE__ */ forwardRef(function NextButtonIcon(props, ref) {
252
+ return _jsx(BuiltInIcon, { ...props, ref: ref, icon: ChevronRight, component: "carousel-nextbuttonicon" });
253
+ });
254
+ // The shared body of the built-in decorative icons above, which only differ by their icon and component name
255
+ const BuiltInIcon = /* @__PURE__ */ forwardRef(function BuiltInIcon({ disableStyle, className, strokeWidth, icon: Icon, component, children, ...props }, ref) {
256
+ const attributes = partAttributes(useCarouselContext('Icon'));
257
+ return cloneElement(children ?? _jsx(Icon, {}), {
258
+ ...attributes,
259
+ 'aria-hidden': true,
260
+ ...props,
261
+ ref,
262
+ strokeWidth: strokeWidth ?? children?.props.strokeWidth ?? 1.5,
263
+ 'data-component': component,
264
+ 'data-style-disabled': disableStyle ? '' : undefined,
265
+ className: twMerge(children?.props.className, className),
266
+ });
267
+ });
268
+ // The shared body of the action buttons: the part's own styled button, or the consumer's child with asChild
269
+ function useActionButton({ asChild, disableStyle, children, ...props }, ref, context, internal) {
270
+ return useRender({
271
+ render: asChild ? children : undefined,
272
+ defaultTagName: 'button',
273
+ ref,
274
+ state: { ...partState(context), disabled: internal.disabled, selected: internal.selected ?? false },
275
+ props: mergeProps({ type: 'button', disabled: internal.disabled, onClick: internal.onClick, ...internal.attributes }, props, asChild ? {} : { children, ...asChildStyleAttributes({ asChild, component: internal.component, disableStyle }) }),
276
+ });
277
+ }
278
+ // The carousel's state every part exposes as data attributes
279
+ function partState(context) {
280
+ return { orientation: context.orientation, disabled: context.disabled, playing: context.playing };
281
+ }
282
+ // The same data attributes, for the icons, which render without useRender
283
+ function partAttributes(context) {
284
+ return {
285
+ 'data-orientation': context.orientation,
286
+ 'data-disabled': context.disabled ? '' : undefined,
287
+ 'data-playing': context.playing ? '' : undefined,
288
+ };
289
+ }
290
+ // Turning the gap into the track's offset, whether it's a number of pixels or a CSS length
291
+ function negativeLength(length) {
292
+ return typeof length === 'number' ? -length : `calc(${length} * -1)`;
293
+ }
294
+ function useCarouselContext(part) {
295
+ const context = useContext(CarouselContext);
296
+ if (!context)
297
+ throw new Error(`Carousel.${part} must be placed inside Carousel.Root.`);
298
+ return context;
299
+ }
@@ -3,6 +3,10 @@ import { Combobox as BaseCombobox } from '@base-ui/react/combobox';
3
3
  import type { ValidationProps } from '../../types/Validation.ts';
4
4
  import type { ComponentProps, ComponentPropsWithRef, ReactElement, ReactNode } from 'react';
5
5
  import type { ValidationMessageChildren } from '../../utils/Validation.ts';
6
+ type IconProps = Omit<SVGIconProps, 'strokeWidth'> & {
7
+ /** @default 1.5 */
8
+ strokeWidth?: SVGIconProps['strokeWidth'];
9
+ };
6
10
  type StyleProps = {
7
11
  /**
8
12
  * Disables preset styling for this element.
@@ -379,26 +383,18 @@ type MessageProps = Omit<ComponentProps<'div'>, 'children'> & StyleProps & {
379
383
  export declare const Error: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
380
384
  /** Shows itself only while Root's validation is `'success'`: with no children it shows the message from `['success', message]`, a function children formats that message, and other children replace it. */
381
385
  export declare const Success: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
382
- /** Built-in decorative open-button icon. Place inside AddonOpenButton; no children needed. Defaults to strokeWidth 1.5. */
383
- export declare const AddonOpenButtonIcon: import("react").ForwardRefExoticComponent<Omit<Omit<SVGIconProps, "strokeWidth"> & {
384
- /** @default 1.5 */
385
- strokeWidth?: SVGIconProps["strokeWidth"];
386
- } & StyleProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
387
- /** Built-in decorative clear icon. Place inside AddonClearButton; no children needed. Defaults to strokeWidth 1.5. */
388
- export declare const AddonClearButtonIcon: import("react").ForwardRefExoticComponent<Omit<Omit<SVGIconProps, "strokeWidth"> & {
389
- /** @default 1.5 */
390
- strokeWidth?: SVGIconProps["strokeWidth"];
391
- } & StyleProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
392
- /** Built-in decorative removal icon. Place inside ChipRemoveButton; no children needed. Defaults to strokeWidth 1.5. */
393
- export declare const ChipRemoveButtonIcon: import("react").ForwardRefExoticComponent<Omit<Omit<SVGIconProps, "strokeWidth"> & {
394
- /** @default 1.5 */
395
- strokeWidth?: SVGIconProps["strokeWidth"];
396
- } & StyleProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
386
+ type BuiltInIconProps = Omit<IconProps, 'children'> & StyleProps & {
387
+ /** Omit for the supplied default icon; pass a child icon only to replace it. */
388
+ children?: ReactElement<SVGIconProps>;
389
+ };
390
+ /** Built-in decorative open-button icon. Place inside AddonOpenButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
391
+ export declare const AddonOpenButtonIcon: import("react").ForwardRefExoticComponent<Omit<BuiltInIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
392
+ /** Built-in decorative clear icon. Place inside AddonClearButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
393
+ export declare const AddonClearButtonIcon: import("react").ForwardRefExoticComponent<Omit<BuiltInIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
394
+ /** Built-in decorative removal icon. Place inside ChipRemoveButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
395
+ export declare const ChipRemoveButtonIcon: import("react").ForwardRefExoticComponent<Omit<BuiltInIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
397
396
  /** Optional spinner inside Addon. Does not detect requests: you decide when to render it. Defaults to strokeWidth 1.5. */
398
- export declare const AddonLoadingIcon: import("react").ForwardRefExoticComponent<Omit<Omit<SVGIconProps, "strokeWidth"> & {
399
- /** @default 1.5 */
400
- strokeWidth?: SVGIconProps["strokeWidth"];
401
- } & StyleProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
397
+ export declare const AddonLoadingIcon: import("react").ForwardRefExoticComponent<Omit<BuiltInIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
402
398
  /** Required selection indicator inside Item. Visibility is automatic and a checkmark is supplied: no children or selection condition needed. Pass children only to replace the built-in checkmark. */
403
399
  export declare const ItemIndicator: import("react").ForwardRefExoticComponent<Omit<Omit<SVGIconProps, "children"> & Pick<import("@base-ui/react").ComboboxItemIndicatorProps, "keepMounted"> & {
404
400
  /** Optional replacement SVG icon. It receives this indicator's props and ref. */
@@ -362,15 +362,15 @@ const Message = /* @__PURE__ */ forwardRef(function Message({ disableStyle, clas
362
362
  const attributes = usePartAttributes();
363
363
  return _jsx("div", { "aria-atomic": "true", ...attributes, ...props, id: describedById, ref: ref, "data-component": component, "data-style-disabled": disableStyle ? '' : undefined, className: className });
364
364
  });
365
- /** Built-in decorative open-button icon. Place inside AddonOpenButton; no children needed. Defaults to strokeWidth 1.5. */
365
+ /** Built-in decorative open-button icon. Place inside AddonOpenButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
366
366
  export const AddonOpenButtonIcon = /* @__PURE__ */ forwardRef(function AddonOpenButtonIcon(props, ref) {
367
367
  return _jsx(BuiltInIcon, { ...props, ref: ref, icon: ChevronsUpDown, component: "combobox-addonopenbuttonicon" });
368
368
  });
369
- /** Built-in decorative clear icon. Place inside AddonClearButton; no children needed. Defaults to strokeWidth 1.5. */
369
+ /** Built-in decorative clear icon. Place inside AddonClearButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
370
370
  export const AddonClearButtonIcon = /* @__PURE__ */ forwardRef(function AddonClearButtonIcon(props, ref) {
371
371
  return _jsx(BuiltInIcon, { ...props, ref: ref, icon: X, component: "combobox-addonclearbuttonicon" });
372
372
  });
373
- /** Built-in decorative removal icon. Place inside ChipRemoveButton; no children needed. Defaults to strokeWidth 1.5. */
373
+ /** Built-in decorative removal icon. Place inside ChipRemoveButton. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
374
374
  export const ChipRemoveButtonIcon = /* @__PURE__ */ forwardRef(function ChipRemoveButtonIcon(props, ref) {
375
375
  return _jsx(BuiltInIcon, { ...props, ref: ref, icon: X, component: "combobox-chipremovebuttonicon" });
376
376
  });
@@ -379,9 +379,18 @@ export const AddonLoadingIcon = /* @__PURE__ */ forwardRef(function AddonLoading
379
379
  return _jsx(BuiltInIcon, { ...props, ref: ref, icon: LoaderCircle, component: "combobox-addonloadingicon" });
380
380
  });
381
381
  // The shared body of the built-in decorative icons above, which only differ by their icon and component name
382
- const BuiltInIcon = /* @__PURE__ */ forwardRef(function BuiltInIcon({ disableStyle, className, strokeWidth = 1.5, icon: Icon, component, ...props }, ref) {
382
+ const BuiltInIcon = /* @__PURE__ */ forwardRef(function BuiltInIcon({ disableStyle, className, strokeWidth, icon: Icon, component, children, ...props }, ref) {
383
383
  const attributes = usePartAttributes();
384
- return _jsx(Icon, { ...attributes, "aria-hidden": true, ...props, ref: ref, strokeWidth: strokeWidth, "data-component": component, "data-style-disabled": disableStyle ? '' : undefined, className: className });
384
+ return cloneElement(children ?? _jsx(Icon, {}), {
385
+ ...attributes,
386
+ 'aria-hidden': true,
387
+ ...props,
388
+ ref,
389
+ strokeWidth: strokeWidth ?? children?.props.strokeWidth ?? 1.5,
390
+ 'data-component': component,
391
+ 'data-style-disabled': disableStyle ? '' : undefined,
392
+ className: twMerge(children?.props.className, className),
393
+ });
385
394
  });
386
395
  /** Required selection indicator inside Item. Visibility is automatic and a checkmark is supplied: no children or selection condition needed. Pass children only to replace the built-in checkmark. */
387
396
  export const ItemIndicator = /* @__PURE__ */ forwardRef(function ItemIndicator({ disableStyle, children, keepMounted, ...props }, ref) {
@@ -28,7 +28,8 @@ import type { ComponentProps, HTMLAttributes, ImgHTMLAttributes, ReactElement }
28
28
  */
29
29
  export declare function Surface({ disableStyle, className, ...props }: HTMLAttributes<HTMLDivElement> & StyleProps): import("react").JSX.Element;
30
30
  export declare function Body({ disableStyle, className, ...props }: ComponentProps<'div'> & StyleProps): import("react").JSX.Element;
31
- export declare function Title({ disableStyle, className, ...props }: HTMLAttributes<HTMLParagraphElement> & StyleProps): import("react").JSX.Element;
31
+ /** Renders an h2, so screen-reader users find it when jumping between headings, whether the panel fills a page or one section of it. */
32
+ export declare function Title({ disableStyle, className, ...props }: HTMLAttributes<HTMLHeadingElement> & StyleProps): import("react").JSX.Element;
32
33
  export declare function Description({ disableStyle, className, ...props }: HTMLAttributes<HTMLParagraphElement> & StyleProps): import("react").JSX.Element;
33
34
  export declare function Image({ disableStyle, className, ...props }: ImgHTMLAttributes<HTMLImageElement> & StyleProps): import("react").JSX.Element;
34
35
  /** Supply the decorative icon as a child. Its classes are preserved; interaction belongs to the surrounding control. */
@@ -33,8 +33,9 @@ export function Surface({ disableStyle, className, ...props }) {
33
33
  export function Body({ disableStyle, className, ...props }) {
34
34
  return (_jsx("div", { ...props, "data-component": "empty-body", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
35
35
  }
36
+ /** Renders an h2, so screen-reader users find it when jumping between headings, whether the panel fills a page or one section of it. */
36
37
  export function Title({ disableStyle, className, ...props }) {
37
- return (_jsx("p", { "data-component": "empty-title", "data-style-disabled": disableStyle ? '' : undefined, className: className, ...props }));
38
+ return (_jsx("h2", { "data-component": "empty-title", "data-style-disabled": disableStyle ? '' : undefined, className: className, ...props }));
38
39
  }
39
40
  export function Description({ disableStyle, className, ...props }) {
40
41
  return (_jsx("p", { "data-component": "empty-description", "data-style-disabled": disableStyle ? '' : undefined, className: className, ...props }));
@@ -0,0 +1,86 @@
1
+ import type { StyleProps } from '../../types/Style.ts';
2
+ import type { ComponentProps, HTMLAttributes, ImgHTMLAttributes, ReactElement, ReactNode } from 'react';
3
+ type Tone = 'neutral' | 'critical';
4
+ /**
5
+ * Explains why something can't be shown, like a page that doesn't exist or a section the user isn't allowed to see. It isn't an error, just a normal state of the app.
6
+ * Root holds the tone and renders nothing itself; Surface and Body render the panel. You decide when it appears. It's a section: put it where the missing content would have been, and it fills that container's height.
7
+ * Image/Icon, ActionsList and HelpText are optional. ActionsList takes your own buttons, since the actions belong to your app.
8
+ *
9
+ * @example A page the user can't access
10
+ * import { Button, Exception, TextLink } from '@central-ui/react'
11
+ * import { Link } from '@tanstack/react-router'
12
+ * import { Lock } from 'lucide-react'
13
+ *
14
+ * function Example() {
15
+ * return (
16
+ * // Inside the body of some page. That page's header shows no title or back button,
17
+ * // since they would describe something the user can't see.
18
+ * <Exception.Root>
19
+ * <Exception.Surface>
20
+ * <Exception.Body>
21
+ * <Exception.Icon>
22
+ * <Lock aria-hidden="true" />
23
+ * </Exception.Icon>
24
+ * <Exception.Title>You don't have access to this page</Exception.Title>
25
+ * <Exception.Description>Ask a store owner to give your account the Orders permission.</Exception.Description>
26
+ * <Exception.ActionsList>
27
+ * <Button.default variant="primary">Go to dashboard</Button.default>
28
+ * </Exception.ActionsList>
29
+ * <Exception.HelpText>
30
+ * Still stuck?{' '}
31
+ * <TextLink.default>
32
+ * <Link to="/support">Contact support</Link>
33
+ * </TextLink.default>
34
+ * </Exception.HelpText>
35
+ * </Exception.Body>
36
+ * </Exception.Surface>
37
+ * </Exception.Root>
38
+ * )
39
+ * }
40
+ *
41
+ * @example A page that doesn't exist
42
+ * // Also inside the body of some page with no title in its header; the code goes in the Title.
43
+ * ;<Exception.Root>
44
+ * <Exception.Surface>
45
+ * <Exception.Body>
46
+ * <Exception.Title>404 · Page not found</Exception.Title>
47
+ * <Exception.Description>The page may have moved, or the link is wrong.</Exception.Description>
48
+ * </Exception.Body>
49
+ * </Exception.Surface>
50
+ * </Exception.Root>
51
+ *
52
+ * @example A section that failed to load, inside a page that otherwise works
53
+ * // In place of that section only; the rest of the page stays.
54
+ * ;<Exception.Root tone="critical">
55
+ * {
56
+ * // Rest of components here
57
+ * }
58
+ * </Exception.Root>
59
+ */
60
+ export declare function Root({ tone, children, }: {
61
+ /**
62
+ * Neutral for expected situations, like no access or a missing page. Critical for failures, like a section that couldn't load; it turns the Title red.
63
+ * Every part carries it as data-tone.
64
+ * @default 'neutral'
65
+ */
66
+ tone?: Tone;
67
+ /** The Surface. */
68
+ children: ReactNode;
69
+ }): import("react").JSX.Element;
70
+ /** Centers the panel and fills its container's height. */
71
+ export declare const Surface: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & StyleProps & import("react").RefAttributes<HTMLDivElement>>;
72
+ export declare const Body: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement> & StyleProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
73
+ /** Renders an h2, so screen-reader users find it when jumping between headings, whether the panel fills a page or one section of it. */
74
+ export declare const Title: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLHeadingElement> & StyleProps & import("react").RefAttributes<HTMLHeadingElement>>;
75
+ export declare const Description: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLParagraphElement> & StyleProps & import("react").RefAttributes<HTMLParagraphElement>>;
76
+ export declare const Image: import("react").ForwardRefExoticComponent<ImgHTMLAttributes<HTMLImageElement> & StyleProps & import("react").RefAttributes<HTMLImageElement>>;
77
+ /** Supply the decorative icon as a child. Its classes are preserved; interaction belongs to the surrounding control. */
78
+ export declare const Icon: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").SVGProps<SVGSVGElement>, "children"> & {
79
+ /** Supply the decorative icon; the wrapper forwards the relevant state and preserves its classes. */
80
+ children: ReactElement<ComponentProps<"svg">>;
81
+ } & StyleProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
82
+ /** Takes your own buttons, like a primary "Go to dashboard"; it creates no actions itself. */
83
+ export declare const ActionsList: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & StyleProps & import("react").RefAttributes<HTMLDivElement>>;
84
+ /** A small footnote below the actions, like "Still stuck? Contact support" with a link. */
85
+ export declare const HelpText: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLParagraphElement> & StyleProps & import("react").RefAttributes<HTMLParagraphElement>>;
86
+ export {};
@@ -0,0 +1,116 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cloneElement, createContext, forwardRef, useContext } from 'react';
3
+ import { twMerge } from 'tailwind-merge';
4
+ const ToneContext = /* @__PURE__ */ createContext(null);
5
+ /**
6
+ * Explains why something can't be shown, like a page that doesn't exist or a section the user isn't allowed to see. It isn't an error, just a normal state of the app.
7
+ * Root holds the tone and renders nothing itself; Surface and Body render the panel. You decide when it appears. It's a section: put it where the missing content would have been, and it fills that container's height.
8
+ * Image/Icon, ActionsList and HelpText are optional. ActionsList takes your own buttons, since the actions belong to your app.
9
+ *
10
+ * @example A page the user can't access
11
+ * import { Button, Exception, TextLink } from '@central-ui/react'
12
+ * import { Link } from '@tanstack/react-router'
13
+ * import { Lock } from 'lucide-react'
14
+ *
15
+ * function Example() {
16
+ * return (
17
+ * // Inside the body of some page. That page's header shows no title or back button,
18
+ * // since they would describe something the user can't see.
19
+ * <Exception.Root>
20
+ * <Exception.Surface>
21
+ * <Exception.Body>
22
+ * <Exception.Icon>
23
+ * <Lock aria-hidden="true" />
24
+ * </Exception.Icon>
25
+ * <Exception.Title>You don't have access to this page</Exception.Title>
26
+ * <Exception.Description>Ask a store owner to give your account the Orders permission.</Exception.Description>
27
+ * <Exception.ActionsList>
28
+ * <Button.default variant="primary">Go to dashboard</Button.default>
29
+ * </Exception.ActionsList>
30
+ * <Exception.HelpText>
31
+ * Still stuck?{' '}
32
+ * <TextLink.default>
33
+ * <Link to="/support">Contact support</Link>
34
+ * </TextLink.default>
35
+ * </Exception.HelpText>
36
+ * </Exception.Body>
37
+ * </Exception.Surface>
38
+ * </Exception.Root>
39
+ * )
40
+ * }
41
+ *
42
+ * @example A page that doesn't exist
43
+ * // Also inside the body of some page with no title in its header; the code goes in the Title.
44
+ * ;<Exception.Root>
45
+ * <Exception.Surface>
46
+ * <Exception.Body>
47
+ * <Exception.Title>404 · Page not found</Exception.Title>
48
+ * <Exception.Description>The page may have moved, or the link is wrong.</Exception.Description>
49
+ * </Exception.Body>
50
+ * </Exception.Surface>
51
+ * </Exception.Root>
52
+ *
53
+ * @example A section that failed to load, inside a page that otherwise works
54
+ * // In place of that section only; the rest of the page stays.
55
+ * ;<Exception.Root tone="critical">
56
+ * {
57
+ * // Rest of components here
58
+ * }
59
+ * </Exception.Root>
60
+ */
61
+ export function Root({ tone = 'neutral', children, }) {
62
+ return _jsx(ToneContext.Provider, { value: tone, children: children });
63
+ }
64
+ /** Centers the panel and fills its container's height. */
65
+ export const Surface = /* @__PURE__ */ forwardRef(function Surface({ disableStyle, className, ...props }, ref) {
66
+ const tone = useTone('Surface');
67
+ return (_jsx("div", { ...props, ref: ref, "data-component": "exception-surface", "data-style-disabled": disableStyle ? '' : undefined, "data-tone": tone, className: className }));
68
+ });
69
+ export const Body = /* @__PURE__ */ forwardRef(function Body({ disableStyle, className, ...props }, ref) {
70
+ const tone = useTone('Body');
71
+ return (_jsx("div", { ...props, ref: ref, "data-component": "exception-body", "data-style-disabled": disableStyle ? '' : undefined, "data-tone": tone, className: className }));
72
+ });
73
+ /** Renders an h2, so screen-reader users find it when jumping between headings, whether the panel fills a page or one section of it. */
74
+ export const Title = /* @__PURE__ */ forwardRef(function Title({ disableStyle, className, ...props }, ref) {
75
+ const tone = useTone('Title');
76
+ return (_jsx("h2", { ...props, ref: ref, "data-component": "exception-title", "data-style-disabled": disableStyle ? '' : undefined, "data-tone": tone, className: className }));
77
+ });
78
+ export const Description = /* @__PURE__ */ forwardRef(function Description({ disableStyle, className, ...props }, ref) {
79
+ const tone = useTone('Description');
80
+ return (_jsx("p", { ...props, ref: ref, "data-component": "exception-description", "data-style-disabled": disableStyle ? '' : undefined, "data-tone": tone, className: className }));
81
+ });
82
+ export const Image = /* @__PURE__ */ forwardRef(function Image({ disableStyle, className, ...props }, ref) {
83
+ const tone = useTone('Image');
84
+ return (_jsx("img", { alt: "", ...props, ref: ref, "data-component": "exception-image", "data-style-disabled": disableStyle ? '' : undefined, "data-tone": tone, className: className }));
85
+ });
86
+ /** Supply the decorative icon as a child. Its classes are preserved; interaction belongs to the surrounding control. */
87
+ export const Icon = /* @__PURE__ */ forwardRef(function Icon({ disableStyle, children, className, ...props }, ref) {
88
+ const tone = useTone('Icon');
89
+ return cloneElement(children, {
90
+ ...props,
91
+ ref,
92
+ ...{
93
+ 'data-component': 'exception-icon',
94
+ 'data-style-disabled': disableStyle ? '' : undefined,
95
+ 'data-tone': tone,
96
+ },
97
+ className: twMerge(children.props.className, className),
98
+ strokeWidth: props.strokeWidth ?? children.props.strokeWidth ?? 1.5,
99
+ });
100
+ });
101
+ /** Takes your own buttons, like a primary "Go to dashboard"; it creates no actions itself. */
102
+ export const ActionsList = /* @__PURE__ */ forwardRef(function ActionsList({ disableStyle, className, ...props }, ref) {
103
+ const tone = useTone('ActionsList');
104
+ return (_jsx("div", { ...props, ref: ref, "data-component": "exception-actionslist", "data-style-disabled": disableStyle ? '' : undefined, "data-tone": tone, className: className }));
105
+ });
106
+ /** A small footnote below the actions, like "Still stuck? Contact support" with a link. */
107
+ export const HelpText = /* @__PURE__ */ forwardRef(function HelpText({ disableStyle, className, ...props }, ref) {
108
+ const tone = useTone('HelpText');
109
+ return (_jsx("p", { ...props, ref: ref, "data-component": "exception-helptext", "data-style-disabled": disableStyle ? '' : undefined, "data-tone": tone, className: className }));
110
+ });
111
+ function useTone(part) {
112
+ const tone = useContext(ToneContext);
113
+ if (!tone)
114
+ throw new Error(`Exception.${part} must be placed inside Exception.Root.`);
115
+ return tone;
116
+ }
@@ -186,8 +186,11 @@ type MessageProps = Omit<ComponentProps<'div'>, 'children'> & StyleProps & {
186
186
  export declare const Error: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
187
187
  /** Shows itself only while Root's validation is `'success'`: with no children it shows the message from `['success', message]`, a function children formats that message, and other children replace it. */
188
188
  export declare const Success: import("react").ForwardRefExoticComponent<Omit<MessageProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
189
- /** Built-in icon; no children needed. Use it inside its matching control. */
190
- export declare const OpenButtonIcon: import("react").ForwardRefExoticComponent<Omit<SVGIconProps & StyleProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
189
+ /** Built-in icon. Use it inside its matching control. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
190
+ export declare const OpenButtonIcon: import("react").ForwardRefExoticComponent<Omit<Omit<SVGIconProps, "children"> & StyleProps & {
191
+ /** Omit for the supplied default icon; pass a child icon only to replace it. */
192
+ children?: ReactElement<SVGIconProps>;
193
+ }, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
191
194
  /** Tracks the surrounding item state automatically. Keep it mounted; do not add your own visibility condition. Use the supplied default visual unless customization is needed. */
192
195
  export declare const ItemIndicator: import("react").ForwardRefExoticComponent<Omit<Omit<SVGIconProps, "children"> & Pick<import("@base-ui/react").SelectItemIndicatorProps, "keepMounted"> & {
193
196
  /** Optional replacement SVG icon. It receives this indicator's props and ref. */
@@ -3,6 +3,7 @@ import { asChildRendering } from "../../utils/asChild.js";
3
3
  import { Select as BaseSelect } from '@base-ui/react/select';
4
4
  import { Check, ChevronDown, ChevronUp, ChevronsUpDown } from "../../utils/icons.js";
5
5
  import { cloneElement, createContext, forwardRef, useContext, useEffect, useId, useState, } from 'react';
6
+ import { twMerge } from 'tailwind-merge';
6
7
  import { readValidation, validationAttributes, validationMessageContent } from "../../utils/Validation.js";
7
8
  /**
8
9
  * Prepare groups in items. A flat-looking list is one group with an empty label and no GroupLabel; keep Group around ItemsList. OpenButtonValue already formats the selection: omit children unless a custom display is needed. Keep ScrollArea → GroupsList → Group → ItemsList → Item in Popup.
@@ -202,10 +203,19 @@ const Message = /* @__PURE__ */ forwardRef(function Message({ disableStyle, clas
202
203
  const attributes = usePartAttributes();
203
204
  return _jsx("div", { "aria-atomic": "true", ...attributes, ...props, id: describedById, ref: ref, "data-component": component, "data-style-disabled": disableStyle ? '' : undefined, className: className });
204
205
  });
205
- /** Built-in icon; no children needed. Use it inside its matching control. */
206
- export const OpenButtonIcon = /* @__PURE__ */ forwardRef(function OpenButtonIcon({ disableStyle, className, strokeWidth = 1.5, ...props }, ref) {
206
+ /** Built-in icon. Use it inside its matching control. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 1.5. */
207
+ export const OpenButtonIcon = /* @__PURE__ */ forwardRef(function OpenButtonIcon({ disableStyle, className, strokeWidth, children, ...props }, ref) {
207
208
  const attributes = usePartAttributes();
208
- return (_jsx(ChevronsUpDown, { ...attributes, "aria-hidden": true, ...props, ref: ref, strokeWidth: strokeWidth, "data-component": "select-openbuttonicon", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
209
+ return cloneElement(children ?? _jsx(ChevronsUpDown, {}), {
210
+ ...attributes,
211
+ 'aria-hidden': true,
212
+ ...props,
213
+ ref,
214
+ strokeWidth: strokeWidth ?? children?.props.strokeWidth ?? 1.5,
215
+ 'data-component': 'select-openbuttonicon',
216
+ 'data-style-disabled': disableStyle ? '' : undefined,
217
+ className: twMerge(children?.props.className, className),
218
+ });
209
219
  });
210
220
  /** Tracks the surrounding item state automatically. Keep it mounted; do not add your own visibility condition. Use the supplied default visual unless customization is needed. */
211
221
  export const ItemIndicator = /* @__PURE__ */ forwardRef(function ItemIndicator({ disableStyle, children, keepMounted, ...props }, ref) {
package/dist/index.d.ts CHANGED
@@ -30,8 +30,10 @@ export * as Badge from './components/badge/index.tsx';
30
30
  export * as Breadcrumb from './components/breadcrumb/index.tsx';
31
31
  export * as Button from './components/button/index.tsx';
32
32
  export * as Card from './components/card/index.tsx';
33
+ export * as Carousel from './components/carousel/index.tsx';
33
34
  export * as Divider from './components/divider/index.tsx';
34
35
  export * as Empty from './components/empty/index.tsx';
36
+ export * as Exception from './components/exception/index.tsx';
35
37
  export * as Form from './components/form/index.tsx';
36
38
  export * as Metric from './components/metric/index.tsx';
37
39
  export * as Page from './components/page/index.tsx';
package/dist/index.js CHANGED
@@ -30,8 +30,10 @@ export * as Badge from "./components/badge/index.js";
30
30
  export * as Breadcrumb from "./components/breadcrumb/index.js";
31
31
  export * as Button from "./components/button/index.js";
32
32
  export * as Card from "./components/card/index.js";
33
+ export * as Carousel from "./components/carousel/index.js";
33
34
  export * as Divider from "./components/divider/index.js";
34
35
  export * as Empty from "./components/empty/index.js";
36
+ export * as Exception from "./components/exception/index.js";
35
37
  export * as Form from "./components/form/index.js";
36
38
  export * as Metric from "./components/metric/index.js";
37
39
  export * as Page from "./components/page/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@central-ui/react",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Accessible React components with optional Tailwind CSS style presets",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -57,6 +57,8 @@
57
57
  "@internationalized/date": "^3.12.4",
58
58
  "@radix-ui/react-password-toggle-field": "^0.2.1",
59
59
  "@tanstack/react-virtual": "^3.14.20",
60
+ "embla-carousel-autoplay": "8.6.0",
61
+ "embla-carousel-react": "8.6.0",
60
62
  "react-aria": "^3.53.1",
61
63
  "react-aria-components": "^1.22.1",
62
64
  "react-joyride": "^3.2.0",
@@ -0,0 +1,62 @@
1
+ /* carousel */
2
+ @layer components {
3
+ :where(:not([data-style-disabled])) {
4
+ &:where([data-component='carousel-slideslist']) {
5
+ @apply w-full rounded-xl;
6
+ &[data-orientation='vertical'] {
7
+ @apply h-72;
8
+ }
9
+ }
10
+
11
+ &:where([data-component='carousel-slide']) {
12
+ @apply basis-full;
13
+ }
14
+
15
+ &:where([data-component='carousel-previousbutton']),
16
+ &:where([data-component='carousel-nextbutton']) {
17
+ @apply flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-full p-0 outline-none focus-visible:outline-2 focus-visible:outline-blue-500 enabled:active:scale-97 transition-[background-color,transform] duration-400 ease-[cubic-bezier(0.2,0.8,0,1)] motion-reduce:transition-none disabled:pointer-events-none disabled:cursor-default;
18
+
19
+ &:not(:disabled) {
20
+ @apply bg-neutral-200 text-neutral-950 hover:bg-neutral-300 focus-visible:bg-neutral-300 active:bg-neutral-300;
21
+ }
22
+ &:disabled {
23
+ @apply bg-black/3 text-neutral-400;
24
+ }
25
+ }
26
+
27
+ &:where([data-component='carousel-previousbuttonicon']),
28
+ &:where([data-component='carousel-nextbuttonicon']) {
29
+ @apply h-4 w-fit shrink-0;
30
+ &[data-orientation='vertical'] {
31
+ @apply rotate-90;
32
+ }
33
+ }
34
+
35
+ &:where([data-component='carousel-indicatorlist']) {
36
+ @apply flex items-center justify-center gap-1.5;
37
+ &[data-orientation='vertical'] {
38
+ @apply flex-col;
39
+ }
40
+ }
41
+
42
+ &:where([data-component='carousel-indicatorbutton']) {
43
+ @apply flex shrink-0 cursor-pointer items-center justify-center p-0 outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 transition-[width,height,background-color,opacity] duration-300 motion-reduce:transition-none disabled:pointer-events-none disabled:opacity-50;
44
+
45
+ /* A dot when empty, stretching into a pill while selected */
46
+ &:empty {
47
+ @apply size-2 rounded-full bg-neutral-300 hover:bg-neutral-400 data-selected:bg-neutral-950;
48
+ &[data-selected][data-orientation='horizontal'] {
49
+ @apply w-5;
50
+ }
51
+ &[data-selected][data-orientation='vertical'] {
52
+ @apply h-5;
53
+ }
54
+ }
55
+
56
+ /* Custom content, like a thumbnail, faded until selected */
57
+ &:not(:empty) {
58
+ @apply overflow-hidden rounded-lg opacity-50 hover:opacity-80 data-selected:opacity-100;
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,40 @@
1
+ /* exception */
2
+ @layer components {
3
+ :where(:not([data-style-disabled])) {
4
+ &:where([data-component='exception-surface']) {
5
+ @apply flex h-full w-full flex-1 flex-col items-center justify-center p-4;
6
+ }
7
+
8
+ &:where([data-component='exception-body']) {
9
+ @apply flex h-fit w-full max-w-150 flex-col items-center justify-center gap-1;
10
+ }
11
+
12
+ &:where([data-component='exception-title']) {
13
+ @apply text-sm font-semibold text-center text-neutral-950;
14
+ }
15
+
16
+ &:where([data-component='exception-title'][data-tone='critical']) {
17
+ @apply text-red-700;
18
+ }
19
+
20
+ &:where([data-component='exception-description']) {
21
+ @apply text-sm font-normal text-center text-neutral-600;
22
+ }
23
+
24
+ &:where([data-component='exception-image']) {
25
+ @apply mb-4 h-16 w-fit;
26
+ }
27
+
28
+ &:where([data-component='exception-icon']) {
29
+ @apply mb-2 h-8 w-fit text-neutral-950;
30
+ }
31
+
32
+ &:where([data-component='exception-actionslist']) {
33
+ @apply mt-3 flex w-fit items-center gap-4;
34
+ }
35
+
36
+ &:where([data-component='exception-helptext']) {
37
+ @apply mt-3 text-xs text-center text-neutral-500;
38
+ }
39
+ }
40
+ }
@@ -29,10 +29,12 @@
29
29
  @import './shopify-polaris-chip.css';
30
30
  @import './shopify-polaris-textlink.css';
31
31
  @import './shopify-polaris-empty.css';
32
+ @import './shopify-polaris-exception.css';
32
33
  @import './shopify-polaris-spinner.css';
33
34
  @import './shopify-polaris-popover.css';
34
35
  @import './shopify-polaris-table.css';
35
36
  @import './shopify-polaris-card.css';
37
+ @import './shopify-polaris-carousel.css';
36
38
  @import './shopify-polaris-skeleton.css';
37
39
  @import './shopify-polaris-avatar.css';
38
40
  @import './shopify-polaris-metric.css';
@@ -0,0 +1,55 @@
1
+ /* carousel */
2
+ @layer components {
3
+ :where(:not([data-style-disabled])) {
4
+ &:where([data-component='carousel-slideslist']) {
5
+ @apply w-full rounded-lg;
6
+ &[data-orientation='vertical'] {
7
+ @apply h-72;
8
+ }
9
+ }
10
+
11
+ &:where([data-component='carousel-slide']) {
12
+ @apply basis-full;
13
+ }
14
+
15
+ &:where([data-component='carousel-previousbutton']),
16
+ &:where([data-component='carousel-nextbutton']) {
17
+ @apply flex size-7.5 shrink-0 cursor-pointer items-center justify-center rounded-lg p-0 bg-white text-neutral-700 outline-none focus-visible:outline-2 focus-visible:outline-blue-500 shadow-[0_-1px_0_0_#b5b5b5_inset,0_0_0_1px_rgba(0,0,0,0.1)_inset,0_0.5px_0_1.5px_#fff_inset] hover:bg-neutral-50 active:pt-[0.1rem] active:shadow-[0px_2px_1px_0px_rgba(0,0,0,0.19)_inset,0px_0px_0px_50px_rgba(0,0,0,0.025)_inset,0px_-1px_1px_0px_rgba(0,0,0,0.1)_inset] disabled:pointer-events-none disabled:opacity-50;
18
+ }
19
+
20
+ &:where([data-component='carousel-previousbuttonicon']),
21
+ &:where([data-component='carousel-nextbuttonicon']) {
22
+ @apply h-4 w-fit shrink-0;
23
+ &[data-orientation='vertical'] {
24
+ @apply rotate-90;
25
+ }
26
+ }
27
+
28
+ &:where([data-component='carousel-indicatorlist']) {
29
+ @apply flex items-center justify-center gap-1.5;
30
+ &[data-orientation='vertical'] {
31
+ @apply flex-col;
32
+ }
33
+ }
34
+
35
+ &:where([data-component='carousel-indicatorbutton']) {
36
+ @apply flex shrink-0 cursor-pointer items-center justify-center p-0 outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 transition-[width,height,background-color,opacity] duration-300 motion-reduce:transition-none disabled:pointer-events-none disabled:opacity-50;
37
+
38
+ /* A dot when empty, stretching into a pill while selected */
39
+ &:empty {
40
+ @apply size-2 rounded-full bg-neutral-300 hover:bg-neutral-400 data-selected:bg-neutral-800;
41
+ &[data-selected][data-orientation='horizontal'] {
42
+ @apply w-5;
43
+ }
44
+ &[data-selected][data-orientation='vertical'] {
45
+ @apply h-5;
46
+ }
47
+ }
48
+
49
+ /* Custom content, like a thumbnail, faded until selected */
50
+ &:not(:empty) {
51
+ @apply overflow-hidden rounded-md opacity-50 hover:opacity-80 data-selected:opacity-100;
52
+ }
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,40 @@
1
+ /* exception */
2
+ @layer components {
3
+ :where(:not([data-style-disabled])) {
4
+ &:where([data-component='exception-surface']) {
5
+ @apply flex h-full w-full flex-1 flex-col items-center justify-center p-4;
6
+ }
7
+
8
+ &:where([data-component='exception-body']) {
9
+ @apply flex h-fit w-full max-w-150 flex-col items-center justify-center gap-1;
10
+ }
11
+
12
+ &:where([data-component='exception-title']) {
13
+ @apply text-base font-semibold text-neutral-900;
14
+ }
15
+
16
+ &:where([data-component='exception-title'][data-tone='critical']) {
17
+ @apply text-red-700;
18
+ }
19
+
20
+ &:where([data-component='exception-description']) {
21
+ @apply text-sm font-medium text-neutral-600;
22
+ }
23
+
24
+ &:where([data-component='exception-image']) {
25
+ @apply mb-4 h-16 w-fit;
26
+ }
27
+
28
+ &:where([data-component='exception-icon']) {
29
+ @apply mb-2 h-8 w-fit text-neutral-900;
30
+ }
31
+
32
+ &:where([data-component='exception-actionslist']) {
33
+ @apply mt-3 flex w-fit items-center gap-4;
34
+ }
35
+
36
+ &:where([data-component='exception-helptext']) {
37
+ @apply mt-3 text-xs text-center text-neutral-500;
38
+ }
39
+ }
40
+ }
@@ -6,6 +6,7 @@
6
6
  @import './shopify-polaris-legacy-breadcrumb.css';
7
7
  @import './shopify-polaris-legacy-button.css';
8
8
  @import './shopify-polaris-legacy-card.css';
9
+ @import './shopify-polaris-legacy-carousel.css';
9
10
  @import './shopify-polaris-legacy-checkbox.css';
10
11
  @import './shopify-polaris-legacy-combobox.css';
11
12
  @import './shopify-polaris-legacy-datepicker.css';
@@ -13,6 +14,7 @@
13
14
  @import './shopify-polaris-legacy-divider.css';
14
15
  @import './shopify-polaris-legacy-drawer.css';
15
16
  @import './shopify-polaris-legacy-empty.css';
17
+ @import './shopify-polaris-legacy-exception.css';
16
18
  @import './shopify-polaris-legacy-fileupload.css';
17
19
  @import './shopify-polaris-legacy-form.css';
18
20
  @import './shopify-polaris-legacy-input.css';
@@ -1,3 +0,0 @@
1
- export type ToneProps = {
2
- tone?: 'neutral' | 'valid' | 'invalid';
3
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- export declare const ToneProvider: import("react").Provider<"neutral" | "valid" | "invalid" | undefined>;
2
- export declare function useToneContext(): "neutral" | "valid" | "invalid" | undefined;
@@ -1,6 +0,0 @@
1
- import { createContext, useContext } from 'react';
2
- const ToneContext = /* @__PURE__ */ createContext('neutral');
3
- export const ToneProvider = ToneContext.Provider;
4
- export function useToneContext() {
5
- return useContext(ToneContext);
6
- }