@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.
- package/dist/components/autocomplete/index.d.ts +9 -5
- package/dist/components/autocomplete/index.js +13 -4
- package/dist/components/carousel/index.d.ts +169 -0
- package/dist/components/carousel/index.js +299 -0
- package/dist/components/combobox/index.d.ts +15 -19
- package/dist/components/combobox/index.js +14 -5
- package/dist/components/empty/index.d.ts +2 -1
- package/dist/components/empty/index.js +2 -1
- package/dist/components/exception/index.d.ts +86 -0
- package/dist/components/exception/index.js +116 -0
- package/dist/components/select/index.d.ts +5 -2
- package/dist/components/select/index.js +13 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +3 -1
- package/src/presets/shopify-polaris/shopify-polaris-carousel.css +62 -0
- package/src/presets/shopify-polaris/shopify-polaris-exception.css +40 -0
- package/src/presets/shopify-polaris/shopify-polaris.css +2 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-carousel.css +55 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-exception.css +40 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy.css +2 -0
- package/dist/types/Tone.d.ts +0 -3
- package/dist/types/Tone.js +0 -1
- package/dist/utils/Tone.d.ts +0 -2
- package/dist/utils/Tone.js +0 -6
|
@@ -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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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<
|
|
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
|
|
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
|
|
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
|
|
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, {
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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<
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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, {
|
|
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
|
-
|
|
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("
|
|
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
|
|
190
|
-
export declare const OpenButtonIcon: import("react").ForwardRefExoticComponent<Omit<SVGIconProps
|
|
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
|
|
206
|
-
export const OpenButtonIcon = /* @__PURE__ */ forwardRef(function OpenButtonIcon({ disableStyle, className, strokeWidth
|
|
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, {
|
|
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.
|
|
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';
|
package/dist/types/Tone.d.ts
DELETED
package/dist/types/Tone.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/utils/Tone.d.ts
DELETED
package/dist/utils/Tone.js
DELETED