@central-ui/react 0.2.0 → 0.4.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/accordion/index.d.ts +14 -14
- package/dist/components/accordion/index.js +19 -19
- package/dist/components/autocomplete/index.d.ts +57 -49
- package/dist/components/autocomplete/index.js +69 -60
- package/dist/components/avatar/index.d.ts +7 -6
- package/dist/components/avatar/index.js +9 -9
- package/dist/components/banner/index.js +1 -1
- package/dist/components/carousel/index.d.ts +189 -0
- package/dist/components/carousel/index.js +303 -0
- package/dist/components/checkbox/index.d.ts +12 -11
- package/dist/components/checkbox/index.js +21 -21
- package/dist/components/combobox/index.d.ts +67 -71
- package/dist/components/combobox/index.js +80 -72
- package/dist/components/datepicker/index.d.ts +8 -9
- package/dist/components/datepicker/index.js +13 -13
- package/dist/components/dialog/index.d.ts +18 -18
- package/dist/components/dialog/index.js +22 -21
- package/dist/components/drawer/index.d.ts +24 -24
- package/dist/components/drawer/index.js +28 -27
- package/dist/components/empty/index.d.ts +2 -1
- package/dist/components/empty/index.js +2 -1
- package/dist/components/exception/index.d.ts +98 -0
- package/dist/components/exception/index.js +116 -0
- package/dist/components/fileupload/index.d.ts +8 -8
- package/dist/components/input/index.d.ts +14 -14
- package/dist/components/input/index.js +27 -27
- package/dist/components/menu/index.d.ts +37 -37
- package/dist/components/menu/index.js +47 -47
- package/dist/components/numberinput/index.d.ts +22 -22
- package/dist/components/numberinput/index.js +35 -35
- package/dist/components/otpinput/index.d.ts +12 -12
- package/dist/components/otpinput/index.js +21 -21
- package/dist/components/passwordinput/index.d.ts +15 -15
- package/dist/components/passwordinput/index.js +27 -27
- package/dist/components/popover/index.d.ts +16 -16
- package/dist/components/popover/index.js +20 -19
- package/dist/components/radio/index.d.ts +12 -12
- package/dist/components/radio/index.js +25 -25
- package/dist/components/ratinginput/index.d.ts +1 -1
- package/dist/components/ratinginput/index.js +3 -3
- package/dist/components/select/index.d.ts +32 -29
- package/dist/components/select/index.js +45 -35
- package/dist/components/slider/index.d.ts +20 -20
- package/dist/components/slider/index.js +27 -27
- package/dist/components/stepper/index.d.ts +8 -7
- package/dist/components/stepper/index.js +4 -2
- package/dist/components/switch/index.d.ts +9 -9
- package/dist/components/switch/index.js +17 -17
- package/dist/components/table/index.d.ts +2 -2
- package/dist/components/table/index.js +16 -16
- package/dist/components/tabs/index.d.ts +10 -10
- package/dist/components/tabs/index.js +11 -11
- package/dist/components/textarea/index.d.ts +13 -13
- package/dist/components/textarea/index.js +25 -25
- package/dist/components/toast/index.d.ts +11 -11
- package/dist/components/toast/index.js +11 -11
- package/dist/components/toggle/index.d.ts +7 -7
- package/dist/components/toggle/index.js +9 -9
- package/dist/components/tooltip/index.d.ts +6 -6
- package/dist/components/tooltip/index.js +6 -7
- package/dist/components/tour/index.d.ts +2 -2
- package/dist/components/tour/index.js +5 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/utils/icons.d.ts +92 -23
- package/dist/utils/icons.js +3 -3
- package/package.json +69 -67
- package/src/presets/shopify-polaris/shopify-polaris-accordion.css +28 -28
- 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-accordion.css +28 -28
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StyleProps } from '../../types/Style.ts';
|
|
2
2
|
import { Avatar as BaseAvatar } from '@base-ui/react/avatar';
|
|
3
|
+
import type { ComponentProps } from 'react';
|
|
3
4
|
export type RootProps = Omit<BaseAvatar.Root.Props, 'render' | 'className'> & {
|
|
4
5
|
/** Choose the shared size for the visible parts in this composition. */
|
|
5
6
|
size?: 'big' | 'medium' | 'small';
|
|
@@ -33,15 +34,15 @@ export type RootProps = Omit<BaseAvatar.Root.Props, 'render' | 'className'> & {
|
|
|
33
34
|
* </Avatar.List>
|
|
34
35
|
*/
|
|
35
36
|
export declare function Root({ children, size, ...props }: RootProps): import("react").JSX.Element;
|
|
36
|
-
export declare
|
|
37
|
-
export declare
|
|
37
|
+
export declare function List({ disableStyle, className, ref, ...props }: ComponentProps<'div'> & StyleProps): import("react").JSX.Element;
|
|
38
|
+
export declare function Image({ disableStyle, className, ref, ...props }: (Omit<BaseAvatar.Image.Props, 'className' | 'render'> & {
|
|
38
39
|
className?: string;
|
|
39
|
-
} & StyleProps
|
|
40
|
-
export declare
|
|
40
|
+
}) & StyleProps): import("react").JSX.Element;
|
|
41
|
+
export declare function FallbackNameInitials({ disableStyle, color, children, className, style, ref, ...props }: (Omit<BaseAvatar.Fallback.Props, 'className' | 'children' | 'render'> & {
|
|
41
42
|
className?: string;
|
|
42
43
|
/** Choose the initials background. The name-to-initials conversion is automatic. */
|
|
43
44
|
color: string;
|
|
44
45
|
/** Pass the full name; initials are derived automatically. Do not shorten it yourself. */
|
|
45
46
|
children: string;
|
|
46
|
-
} & StyleProps
|
|
47
|
-
export declare
|
|
47
|
+
}) & StyleProps): import("react").JSX.Element;
|
|
48
|
+
export declare function FallbackImage({ disableStyle, className, delay, ref, ...props }: (ComponentProps<'img'> & Pick<BaseAvatar.Fallback.Props, 'delay'>) & StyleProps): import("react").JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Avatar as BaseAvatar } from '@base-ui/react/avatar';
|
|
3
|
-
import { createContext,
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
4
|
/**
|
|
5
5
|
* Root supplies sizing and image/fallback coordination. Image loading chooses the fallback automatically; do not implement your own image-error state. Choose initials or an image fallback.
|
|
6
6
|
*
|
|
@@ -33,14 +33,14 @@ export function Root({ children, size = 'medium', ...props }) {
|
|
|
33
33
|
// Native Root renders a span; the fragment preserves our wrapper-free Root contract.
|
|
34
34
|
return (_jsx(SizeContext.Provider, { value: size, children: _jsx(BaseAvatar.Root, { ...props, render: (rootProps) => _jsx(_Fragment, { children: rootProps.children }), children: children }) }));
|
|
35
35
|
}
|
|
36
|
-
export
|
|
36
|
+
export function List({ disableStyle, className, ref, ...props }) {
|
|
37
37
|
return (_jsx("div", { ...props, ref: ref, "data-component": "avatar-list", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
|
|
38
|
-
}
|
|
39
|
-
export
|
|
38
|
+
}
|
|
39
|
+
export function Image({ disableStyle, className, ref, ...props }) {
|
|
40
40
|
const size = useContext(SizeContext);
|
|
41
41
|
return (_jsx(BaseAvatar.Image, { ...props, ref: ref, "data-size": size, "data-component": "avatar-image", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
|
|
42
|
-
}
|
|
43
|
-
export
|
|
42
|
+
}
|
|
43
|
+
export function FallbackNameInitials({ disableStyle, color, children, className, style, ref, ...props }) {
|
|
44
44
|
const size = useContext(SizeContext);
|
|
45
45
|
return (_jsx(BaseAvatar.Fallback, { ...props, ref: ref, "data-size": size, role: "img", "aria-label": children, style: typeof style === 'function'
|
|
46
46
|
? (state) => ({ backgroundColor: color, ...style(state) })
|
|
@@ -51,10 +51,10 @@ export const FallbackNameInitials = /* @__PURE__ */ forwardRef(function Fallback
|
|
|
51
51
|
.map((word) => word[0].toUpperCase())
|
|
52
52
|
.join('')
|
|
53
53
|
.slice(0, 2) }));
|
|
54
|
-
}
|
|
55
|
-
export
|
|
54
|
+
}
|
|
55
|
+
export function FallbackImage({ disableStyle, className, delay, ref, ...props }) {
|
|
56
56
|
const size = useContext(SizeContext);
|
|
57
57
|
return (_jsx(BaseAvatar.Fallback, { delay: delay, render: _jsx("img", { ...props, ref: ref }), "data-size": size, "data-component": "avatar-fallbackimage", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
|
|
58
|
-
}
|
|
58
|
+
}
|
|
59
59
|
const SizeContext =
|
|
60
60
|
/* @__PURE__ */ createContext('medium');
|
|
@@ -104,7 +104,7 @@ export function CloseButtonIcon({ disableStyle, children = _jsx(X, {}), classNam
|
|
|
104
104
|
'data-style-disabled': disableStyle ? '' : undefined,
|
|
105
105
|
},
|
|
106
106
|
className: twMerge(children.props.className, className),
|
|
107
|
-
strokeWidth: props.strokeWidth ?? children.props.strokeWidth ??
|
|
107
|
+
strokeWidth: props.strokeWidth ?? children.props.strokeWidth ?? 2,
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
export function Body({ disableStyle, className, ...props }) {
|
|
@@ -0,0 +1,189 @@
|
|
|
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
|
+
* How far a drag must move, in pixels, before it counts as a drag instead of a click on the slide's content.
|
|
41
|
+
* @default 10
|
|
42
|
+
*/
|
|
43
|
+
dragThreshold?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Whether a hard swipe may fly past several slides instead of stopping on the next one.
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
skipSnaps?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* How long a move to another slide takes, from 20 (fast) to 60 (slow). Not in milliseconds.
|
|
51
|
+
* @default 25
|
|
52
|
+
*/
|
|
53
|
+
duration?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Removes the empty space at the start and end when slides are narrower than SlidesList. 'trimSnaps' also drops the positions landing in that space, 'keepSnaps' keeps them, false keeps the space. Ignored with loop.
|
|
56
|
+
* @default 'trimSnaps'
|
|
57
|
+
*/
|
|
58
|
+
containScroll?: false | 'trimSnaps' | 'keepSnaps';
|
|
59
|
+
/**
|
|
60
|
+
* 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.
|
|
61
|
+
* @default 0
|
|
62
|
+
*/
|
|
63
|
+
gap?: number | string;
|
|
64
|
+
/**
|
|
65
|
+
* Turns off dragging and every button.
|
|
66
|
+
* @default false
|
|
67
|
+
*/
|
|
68
|
+
disabled?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Moves to the next slide on its own. Pass true for the defaults, or an object to change them:
|
|
71
|
+
* - delay: milliseconds between slides (default 4000)
|
|
72
|
+
* - jump: jump instead of animating (default false)
|
|
73
|
+
* - playOnInit: start playing as soon as the carousel mounts (default true)
|
|
74
|
+
* - 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)
|
|
75
|
+
* - stopOnMouseEnter: stop while the mouse is over SlidesList; it resumes on mouse leave only with stopOnInteraction false (default false)
|
|
76
|
+
* - stopOnFocusIn: stop when focus moves inside SlidesList; it resumes on focus out only with stopOnInteraction false (default true)
|
|
77
|
+
* - stopOnLastSnap: stop on the last slide (default false)
|
|
78
|
+
* @default false
|
|
79
|
+
*/
|
|
80
|
+
autoplay?: boolean | Omit<AutoplayOptionsType, 'active' | 'breakpoints' | 'rootNode'>;
|
|
81
|
+
/** The selected slide's position, from 0. Use with onIndexChange to control it yourself. */
|
|
82
|
+
index?: number;
|
|
83
|
+
/**
|
|
84
|
+
* The slide the carousel starts on, when `index` isn't controlled.
|
|
85
|
+
* @default 0
|
|
86
|
+
*/
|
|
87
|
+
defaultIndex?: number;
|
|
88
|
+
/** Called with the new position whenever another slide gets selected, by a drag, a button, or autoplay. */
|
|
89
|
+
onIndexChange?: (index: number) => void;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* A row of slides users swipe through, built on Embla Carousel. Root holds the carousel's state and renders no element;
|
|
93
|
+
* SlidesList is the visible window and Slide is one item, written by hand so each can hold different content.
|
|
94
|
+
* Previous/Next and Indicator buttons work on their own: no click handlers needed.
|
|
95
|
+
* Every part carries data-orientation, data-disabled, and data-playing (while autoplay runs), so any part can be styled by the carousel's state.
|
|
96
|
+
*
|
|
97
|
+
* @example Complete composition
|
|
98
|
+
* import { Carousel } from '@central-ui/react'
|
|
99
|
+
*
|
|
100
|
+
* function Example() {
|
|
101
|
+
* return (
|
|
102
|
+
* <Carousel.Root loop>
|
|
103
|
+
* <Carousel.SlidesList>
|
|
104
|
+
* {products.map((product) => (
|
|
105
|
+
* <Carousel.Slide key={product.id}>
|
|
106
|
+
* <img src={product.image} alt={product.title} />
|
|
107
|
+
* </Carousel.Slide>
|
|
108
|
+
* ))}
|
|
109
|
+
* </Carousel.SlidesList>
|
|
110
|
+
* <Carousel.PreviousButton aria-label="Previous slide">
|
|
111
|
+
* <Carousel.PreviousButtonIcon />
|
|
112
|
+
* </Carousel.PreviousButton>
|
|
113
|
+
* <Carousel.NextButton aria-label="Next slide">
|
|
114
|
+
* <Carousel.NextButtonIcon />
|
|
115
|
+
* </Carousel.NextButton>
|
|
116
|
+
* <Carousel.IndicatorList>
|
|
117
|
+
* {(index) => <Carousel.IndicatorButton index={index} />}
|
|
118
|
+
* </Carousel.IndicatorList>
|
|
119
|
+
* </Carousel.Root>
|
|
120
|
+
* )
|
|
121
|
+
* }
|
|
122
|
+
*
|
|
123
|
+
* @example Several slides at once
|
|
124
|
+
* // Size slides with Tailwind (responsive classes work); gap is subtracted from that size.
|
|
125
|
+
* ;<Carousel.Root gap={12} align="start" slidesToScroll="auto">
|
|
126
|
+
* <Carousel.SlidesList>
|
|
127
|
+
* <Carousel.Slide className="basis-full md:basis-1/3">…</Carousel.Slide>
|
|
128
|
+
* </Carousel.SlidesList>
|
|
129
|
+
* </Carousel.Root>
|
|
130
|
+
*
|
|
131
|
+
* @example Autoplay, pausing while hovered and resuming on mouse leave
|
|
132
|
+
* ;<Carousel.Root loop autoplay={{ delay: 5000, stopOnMouseEnter: true, stopOnInteraction: false }}>
|
|
133
|
+
*
|
|
134
|
+
* @example Controlled position
|
|
135
|
+
* const [index, setIndex] = useState(0)
|
|
136
|
+
* ;<Carousel.Root index={index} onIndexChange={setIndex}>
|
|
137
|
+
*/
|
|
138
|
+
export declare function Root({ orientation, loop, align, slidesToScroll, dragFree, dragThreshold, skipSnaps, duration, containScroll, gap, disabled, autoplay, index, defaultIndex, onIndexChange, children, }: RootProps): import("react").JSX.Element;
|
|
139
|
+
/** 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. */
|
|
140
|
+
export declare function SlidesList({ disableStyle, style, children, ref, ...props }: ComponentProps<'div'> & StyleProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
141
|
+
/**
|
|
142
|
+
* One slide; each can hold different content. Size it with a className, like `basis-1/3`; it fills SlidesList by default.
|
|
143
|
+
* Its padding makes the gap between slides, so put backgrounds, borders and radius on its content, not on the Slide itself.
|
|
144
|
+
*/
|
|
145
|
+
export declare function Slide({ disableStyle, style, ref, ...props }: ComponentProps<'div'> & StyleProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
146
|
+
type ButtonPartProps = ComponentProps<'button'> & AsChildProps & StyleProps;
|
|
147
|
+
/** 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. */
|
|
148
|
+
export declare function PreviousButton({ ref, ...props }: ButtonPartProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
149
|
+
/** 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. */
|
|
150
|
+
export declare function NextButton({ ref, ...props }: ButtonPartProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
151
|
+
/**
|
|
152
|
+
* Renders one IndicatorButton per position through its children function, which receives that position's index.
|
|
153
|
+
* The count comes from the carousel itself: with several slides per view, it counts positions, not slides.
|
|
154
|
+
*
|
|
155
|
+
* @example Dots
|
|
156
|
+
* ;<Carousel.IndicatorList>
|
|
157
|
+
* {(index) => <Carousel.IndicatorButton index={index} />}
|
|
158
|
+
* </Carousel.IndicatorList>
|
|
159
|
+
*/
|
|
160
|
+
export declare function IndicatorList({ disableStyle, children, ref, ...props }: Omit<ComponentProps<'div'>, 'children'> & StyleProps & {
|
|
161
|
+
/** Called once per position with its index; return an IndicatorButton. */
|
|
162
|
+
children: (index: number) => ReactNode;
|
|
163
|
+
}): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
164
|
+
/**
|
|
165
|
+
* Moves to its position when clicked, and carries data-selected while that position is shown. Shows as a dot when empty.
|
|
166
|
+
* Pass asChild to give this behavior to your own button instead.
|
|
167
|
+
*
|
|
168
|
+
* @example Thumbnails instead of dots
|
|
169
|
+
* ;<Carousel.IndicatorList>
|
|
170
|
+
* {(index) => (
|
|
171
|
+
* <Carousel.IndicatorButton index={index}>
|
|
172
|
+
* <img src={images[index].thumbnail} alt="" />
|
|
173
|
+
* </Carousel.IndicatorButton>
|
|
174
|
+
* )}
|
|
175
|
+
* </Carousel.IndicatorList>
|
|
176
|
+
*/
|
|
177
|
+
export declare function IndicatorButton({ index, ref, ...props }: ButtonPartProps & {
|
|
178
|
+
/** The position this button moves to, from IndicatorList's children function. */
|
|
179
|
+
index: number;
|
|
180
|
+
}): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
181
|
+
type BuiltInIconProps = Omit<IconProps, 'children'> & StyleProps & {
|
|
182
|
+
/** Omit for the supplied default icon; pass a child icon only to replace it. */
|
|
183
|
+
children?: ReactElement<SVGIconProps>;
|
|
184
|
+
};
|
|
185
|
+
/** 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. */
|
|
186
|
+
export declare function PreviousButtonIcon({ ref, ...props }: BuiltInIconProps): import("react").JSX.Element;
|
|
187
|
+
/** 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. */
|
|
188
|
+
export declare function NextButtonIcon({ ref, ...props }: BuiltInIconProps): import("react").JSX.Element;
|
|
189
|
+
export {};
|
|
@@ -0,0 +1,303 @@
|
|
|
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, 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, dragThreshold = 10, skipSnaps = false, duration = 25, containScroll = 'trimSnaps', 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
|
+
dragThreshold,
|
|
71
|
+
skipSnaps,
|
|
72
|
+
duration,
|
|
73
|
+
containScroll,
|
|
74
|
+
startIndex,
|
|
75
|
+
active: !disabled,
|
|
76
|
+
}, autoplay ? [Autoplay(autoplay === true ? {} : autoplay)] : []);
|
|
77
|
+
const [snapshot, setSnapshot] = useState({
|
|
78
|
+
selectedIndex: startIndex,
|
|
79
|
+
snapCount: 0,
|
|
80
|
+
canScrollPrevious: false,
|
|
81
|
+
canScrollNext: false,
|
|
82
|
+
playing: false,
|
|
83
|
+
});
|
|
84
|
+
//1. Mirroring Embla's state, so the parts re-render when it changes
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (!api)
|
|
87
|
+
return;
|
|
88
|
+
function sync(carousel, event) {
|
|
89
|
+
setSnapshot({
|
|
90
|
+
selectedIndex: carousel.selectedScrollSnap(),
|
|
91
|
+
snapCount: carousel.scrollSnapList().length,
|
|
92
|
+
canScrollPrevious: carousel.canScrollPrev(),
|
|
93
|
+
canScrollNext: carousel.canScrollNext(),
|
|
94
|
+
playing: readPlaying(carousel, event),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// The plugin fires play/stop before updating isPlaying(), so those events decide it themselves
|
|
98
|
+
function readPlaying(carousel, event) {
|
|
99
|
+
if (event === 'autoplay:play')
|
|
100
|
+
return true;
|
|
101
|
+
if (event === 'autoplay:stop')
|
|
102
|
+
return false;
|
|
103
|
+
// Embla starts no plugins while disabled, so plugins() is undefined then
|
|
104
|
+
return carousel.plugins()?.autoplay?.isPlaying() ?? false;
|
|
105
|
+
}
|
|
106
|
+
sync(api);
|
|
107
|
+
api.on('select', sync).on('reInit', sync).on('autoplay:play', sync).on('autoplay:stop', sync);
|
|
108
|
+
return () => {
|
|
109
|
+
api.off('select', sync).off('reInit', sync).off('autoplay:play', sync).off('autoplay:stop', sync);
|
|
110
|
+
};
|
|
111
|
+
}, [api]);
|
|
112
|
+
//2. Reporting every newly selected slide
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (!api || !onIndexChange)
|
|
115
|
+
return;
|
|
116
|
+
function handleSelect(carousel) {
|
|
117
|
+
onIndexChange?.(carousel.selectedScrollSnap());
|
|
118
|
+
}
|
|
119
|
+
api.on('select', handleSelect);
|
|
120
|
+
return () => {
|
|
121
|
+
api.off('select', handleSelect);
|
|
122
|
+
};
|
|
123
|
+
}, [api, onIndexChange]);
|
|
124
|
+
//3. Moving to a controlled index set from outside
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!api || index === undefined || index === api.selectedScrollSnap())
|
|
127
|
+
return;
|
|
128
|
+
api.scrollTo(index);
|
|
129
|
+
}, [api, index]);
|
|
130
|
+
const context = { emblaRef, api, orientation, disabled, gap, ...snapshot };
|
|
131
|
+
return _jsx(CarouselContext.Provider, { value: context, children: children });
|
|
132
|
+
}
|
|
133
|
+
/** 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. */
|
|
134
|
+
export function SlidesList({ disableStyle, style, children, ref, ...props }) {
|
|
135
|
+
const context = useCarouselContext('SlidesList');
|
|
136
|
+
const vertical = context.orientation === 'vertical';
|
|
137
|
+
// The track Embla moves; its negative margin cancels the first slide's gap
|
|
138
|
+
const track = (_jsx("div", { style: {
|
|
139
|
+
display: 'flex',
|
|
140
|
+
flexDirection: vertical ? 'column' : 'row',
|
|
141
|
+
height: vertical ? '100%' : undefined,
|
|
142
|
+
touchAction: vertical ? 'pan-x pinch-zoom' : 'pan-y pinch-zoom',
|
|
143
|
+
[vertical ? 'marginBlockStart' : 'marginInlineStart']: negativeLength(context.gap),
|
|
144
|
+
}, children: children }));
|
|
145
|
+
return useRender({
|
|
146
|
+
ref: [ref ?? null, context.emblaRef],
|
|
147
|
+
state: partState(context),
|
|
148
|
+
props: {
|
|
149
|
+
role: 'region',
|
|
150
|
+
'aria-roledescription': 'carousel',
|
|
151
|
+
...props,
|
|
152
|
+
style: { overflow: 'hidden', ...style },
|
|
153
|
+
'data-component': 'carousel-slideslist',
|
|
154
|
+
'data-style-disabled': disableStyle ? '' : undefined,
|
|
155
|
+
children: track,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* One slide; each can hold different content. Size it with a className, like `basis-1/3`; it fills SlidesList by default.
|
|
161
|
+
* Its padding makes the gap between slides, so put backgrounds, borders and radius on its content, not on the Slide itself.
|
|
162
|
+
*/
|
|
163
|
+
export function Slide({ disableStyle, style, ref, ...props }) {
|
|
164
|
+
const context = useCarouselContext('Slide');
|
|
165
|
+
const vertical = context.orientation === 'vertical';
|
|
166
|
+
return useRender({
|
|
167
|
+
ref,
|
|
168
|
+
state: partState(context),
|
|
169
|
+
props: {
|
|
170
|
+
role: 'group',
|
|
171
|
+
'aria-roledescription': 'slide',
|
|
172
|
+
...props,
|
|
173
|
+
style: {
|
|
174
|
+
flexShrink: 0,
|
|
175
|
+
minWidth: 0,
|
|
176
|
+
minHeight: 0,
|
|
177
|
+
boxSizing: 'border-box',
|
|
178
|
+
[vertical ? 'paddingBlockStart' : 'paddingInlineStart']: context.gap,
|
|
179
|
+
...style,
|
|
180
|
+
},
|
|
181
|
+
'data-component': 'carousel-slide',
|
|
182
|
+
'data-style-disabled': disableStyle ? '' : undefined,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/** 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. */
|
|
187
|
+
export function PreviousButton({ ref, ...props }) {
|
|
188
|
+
const context = useCarouselContext('PreviousButton');
|
|
189
|
+
return useActionButton(props, ref, context, {
|
|
190
|
+
component: 'carousel-previousbutton',
|
|
191
|
+
disabled: context.disabled || !context.canScrollPrevious,
|
|
192
|
+
onClick: () => context.api?.scrollPrev(),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/** 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. */
|
|
196
|
+
export function NextButton({ ref, ...props }) {
|
|
197
|
+
const context = useCarouselContext('NextButton');
|
|
198
|
+
return useActionButton(props, ref, context, {
|
|
199
|
+
component: 'carousel-nextbutton',
|
|
200
|
+
disabled: context.disabled || !context.canScrollNext,
|
|
201
|
+
onClick: () => context.api?.scrollNext(),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Renders one IndicatorButton per position through its children function, which receives that position's index.
|
|
206
|
+
* The count comes from the carousel itself: with several slides per view, it counts positions, not slides.
|
|
207
|
+
*
|
|
208
|
+
* @example Dots
|
|
209
|
+
* ;<Carousel.IndicatorList>
|
|
210
|
+
* {(index) => <Carousel.IndicatorButton index={index} />}
|
|
211
|
+
* </Carousel.IndicatorList>
|
|
212
|
+
*/
|
|
213
|
+
export function IndicatorList({ disableStyle, children, ref, ...props }) {
|
|
214
|
+
const context = useCarouselContext('IndicatorList');
|
|
215
|
+
return useRender({
|
|
216
|
+
ref,
|
|
217
|
+
state: partState(context),
|
|
218
|
+
props: {
|
|
219
|
+
...props,
|
|
220
|
+
'data-component': 'carousel-indicatorlist',
|
|
221
|
+
'data-style-disabled': disableStyle ? '' : undefined,
|
|
222
|
+
children: Array.from({ length: context.snapCount }, (_, index) => _jsx(Fragment, { children: children(index) }, index)),
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Moves to its position when clicked, and carries data-selected while that position is shown. Shows as a dot when empty.
|
|
228
|
+
* Pass asChild to give this behavior to your own button instead.
|
|
229
|
+
*
|
|
230
|
+
* @example Thumbnails instead of dots
|
|
231
|
+
* ;<Carousel.IndicatorList>
|
|
232
|
+
* {(index) => (
|
|
233
|
+
* <Carousel.IndicatorButton index={index}>
|
|
234
|
+
* <img src={images[index].thumbnail} alt="" />
|
|
235
|
+
* </Carousel.IndicatorButton>
|
|
236
|
+
* )}
|
|
237
|
+
* </Carousel.IndicatorList>
|
|
238
|
+
*/
|
|
239
|
+
export function IndicatorButton({ index, ref, ...props }) {
|
|
240
|
+
const context = useCarouselContext('IndicatorButton');
|
|
241
|
+
const selected = context.selectedIndex === index;
|
|
242
|
+
return useActionButton(props, ref, context, {
|
|
243
|
+
component: 'carousel-indicatorbutton',
|
|
244
|
+
disabled: context.disabled,
|
|
245
|
+
selected,
|
|
246
|
+
attributes: { 'aria-label': `Go to slide ${index + 1}`, 'aria-current': selected ? 'true' : undefined },
|
|
247
|
+
onClick: () => context.api?.scrollTo(index),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/** 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. */
|
|
251
|
+
export function PreviousButtonIcon({ ref, ...props }) {
|
|
252
|
+
return _jsx(BuiltInIcon, { ...props, ref: ref, icon: ChevronLeft, component: "carousel-previousbuttonicon" });
|
|
253
|
+
}
|
|
254
|
+
/** 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. */
|
|
255
|
+
export function NextButtonIcon({ ref, ...props }) {
|
|
256
|
+
return _jsx(BuiltInIcon, { ...props, ref: ref, icon: ChevronRight, component: "carousel-nextbuttonicon" });
|
|
257
|
+
}
|
|
258
|
+
// The shared body of the built-in decorative icons above, which only differ by their icon and component name
|
|
259
|
+
function BuiltInIcon({ disableStyle, className, strokeWidth, icon: Icon, component, children, ref, ...props }) {
|
|
260
|
+
const attributes = partAttributes(useCarouselContext('Icon'));
|
|
261
|
+
return cloneElement(children ?? _jsx(Icon, {}), {
|
|
262
|
+
...attributes,
|
|
263
|
+
'aria-hidden': true,
|
|
264
|
+
...props,
|
|
265
|
+
ref,
|
|
266
|
+
strokeWidth: strokeWidth ?? children?.props.strokeWidth ?? 1.5,
|
|
267
|
+
'data-component': component,
|
|
268
|
+
'data-style-disabled': disableStyle ? '' : undefined,
|
|
269
|
+
className: twMerge(children?.props.className, className),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
// The shared body of the action buttons: the part's own styled button, or the consumer's child with asChild
|
|
273
|
+
function useActionButton({ asChild, disableStyle, children, ...props }, ref, context, internal) {
|
|
274
|
+
return useRender({
|
|
275
|
+
render: asChild ? children : undefined,
|
|
276
|
+
defaultTagName: 'button',
|
|
277
|
+
ref,
|
|
278
|
+
state: { ...partState(context), disabled: internal.disabled, selected: internal.selected ?? false },
|
|
279
|
+
props: mergeProps({ type: 'button', disabled: internal.disabled, onClick: internal.onClick, ...internal.attributes }, props, asChild ? {} : { children, ...asChildStyleAttributes({ asChild, component: internal.component, disableStyle }) }),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// The carousel's state every part exposes as data attributes
|
|
283
|
+
function partState(context) {
|
|
284
|
+
return { orientation: context.orientation, disabled: context.disabled, playing: context.playing };
|
|
285
|
+
}
|
|
286
|
+
// The same data attributes, for the icons, which render without useRender
|
|
287
|
+
function partAttributes(context) {
|
|
288
|
+
return {
|
|
289
|
+
'data-orientation': context.orientation,
|
|
290
|
+
'data-disabled': context.disabled ? '' : undefined,
|
|
291
|
+
'data-playing': context.playing ? '' : undefined,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
// Turning the gap into the track's offset, whether it's a number of pixels or a CSS length
|
|
295
|
+
function negativeLength(length) {
|
|
296
|
+
return typeof length === 'number' ? -length : `calc(${length} * -1)`;
|
|
297
|
+
}
|
|
298
|
+
function useCarouselContext(part) {
|
|
299
|
+
const context = useContext(CarouselContext);
|
|
300
|
+
if (!context)
|
|
301
|
+
throw new Error(`Carousel.${part} must be placed inside Carousel.Root.`);
|
|
302
|
+
return context;
|
|
303
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StyleProps } from '../../types/Style.ts';
|
|
2
2
|
import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';
|
|
3
|
+
import { CheckboxGroup as BaseCheckboxGroup } from '@base-ui/react/checkbox-group';
|
|
3
4
|
import type { ComponentProps, ReactNode } from 'react';
|
|
4
5
|
import type { ValidationProps } from '../../types/Validation.ts';
|
|
5
6
|
import type { ValidationMessageChildren } from '../../utils/Validation.ts';
|
|
@@ -44,25 +45,25 @@ export type RootProps = Pick<BaseCheckbox.Root.Props, CoreKeys> & ValidationProp
|
|
|
44
45
|
*/
|
|
45
46
|
export declare function Root({ children, id, validation, ...coreProps }: RootProps): import("react").JSX.Element;
|
|
46
47
|
/** Optional: controls multiple checkboxes as a native array of checked values. Put individual Roots inside and give each child a value. For a select-all checkbox, use parent on its Root and provide allValues on this controlled group; Base UI derives checked and indeterminate automatically. */
|
|
47
|
-
export declare
|
|
48
|
+
export declare function CheckboxGroup({ disableStyle, className, ref, ...props }: (Omit<BaseCheckboxGroup.Props, 'className'> & {
|
|
48
49
|
className?: string;
|
|
49
|
-
} & StyleProps
|
|
50
|
-
export declare
|
|
51
|
-
export declare
|
|
50
|
+
}) & StyleProps): import("react").JSX.Element;
|
|
51
|
+
export declare function Container({ disableStyle, className, ref, ...props }: ComponentProps<'div'> & StyleProps): import("react").JSX.Element;
|
|
52
|
+
export declare function Button({ disableStyle, className, ref, ...props }: (Omit<BaseCheckbox.Root.Props, CoreKeys | 'className' | 'render' | 'nativeButton'> & {
|
|
52
53
|
className?: string;
|
|
53
|
-
} & StyleProps
|
|
54
|
+
}) & StyleProps): import("react").JSX.Element;
|
|
54
55
|
/** 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. */
|
|
55
|
-
export declare
|
|
56
|
+
export declare function Indicator({ disableStyle, className, ref, ...props }: (Omit<BaseCheckbox.Indicator.Props, 'className' | 'children' | 'render'> & {
|
|
56
57
|
className?: string;
|
|
57
|
-
} & StyleProps
|
|
58
|
+
}) & StyleProps): import("react").JSX.Element;
|
|
58
59
|
/** Labels the associated control automatically. Supply the visible text; do not wire a second label association. */
|
|
59
|
-
export declare
|
|
60
|
-
export declare
|
|
60
|
+
export declare function Label({ disableStyle, className, ref, ...props }: Omit<ComponentProps<'label'>, 'htmlFor'> & StyleProps): import("react").JSX.Element;
|
|
61
|
+
export declare function Description({ disableStyle, className, id, ref, ...props }: ComponentProps<'span'> & StyleProps): import("react").JSX.Element;
|
|
61
62
|
type MessageProps = Omit<ComponentProps<'span'>, 'children'> & StyleProps & {
|
|
62
63
|
children?: ValidationMessageChildren;
|
|
63
64
|
};
|
|
64
65
|
/** Shows itself only while Root's validation is `'error'`: with no children it shows the message from `['error', message]`, a function children formats that message, and other children replace it. */
|
|
65
|
-
export declare
|
|
66
|
+
export declare function Error({ children, ref, ...props }: MessageProps): import("react").JSX.Element | null;
|
|
66
67
|
/** 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. */
|
|
67
|
-
export declare
|
|
68
|
+
export declare function Success({ children, ref, ...props }: MessageProps): import("react").JSX.Element | null;
|
|
68
69
|
export {};
|