@central-ui/react 0.4.0 → 0.5.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.
@@ -1,101 +1,222 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { asChildStyleAttributes } from "../../utils/asChild.js";
3
- import { Steps, useSteps as useArkSteps } from '@ark-ui/react/steps';
3
+ import { Steps, useStepsItemContext } from '@ark-ui/react/steps';
4
4
  import { Check } from "../../utils/icons.js";
5
- // Creates a controlled steps state machine for RootProvider.
6
- export function useSteps(props) {
7
- return useArkSteps({ orientation: 'vertical', ...props });
8
- }
9
- // Reads the state of the nearest Steps root.
10
- export { useStepsContext } from '@ark-ui/react/steps';
11
- // Reads the state of the nearest Steps item.
12
- export { useStepsItemContext } from '@ark-ui/react/steps';
5
+ import { cloneElement, createContext, useContext } from 'react';
6
+ import { twMerge } from 'tailwind-merge';
7
+ const StepperContext = /* @__PURE__ */ createContext(null);
13
8
  /**
14
- * Root owns the active step. Keep each Item in List with its index, and match Content indices to those items. NextButton/PreviousButton navigate between steps; pass asChild to give their behavior to your own buttons instead. Indicator reflects progress automatically; CompletedContent is shown after the last step.
9
+ * Splits a long task, like a checkout or an onboarding form, into steps the user goes through one by one.
10
+ * Root holds the current step and renders nothing itself. List every step's value in items, in order, then give each Item and Content one of them, like Tabs.
11
+ * Each Content holds its step's Body and Footer, and that Footer holds the step's own PreviousButton and NextButton, so every step words its buttons for itself, like "I agree" or "Place order". Never place the buttons outside a Content.
12
+ * NextButton on the last step finishes them all, showing CompletedContent. Indicator, IndicatorIcon, IndicatorText and Separator follow progress automatically.
15
13
  *
16
14
  * @example Complete composition
17
15
  * import { Button, Stepper } from '@central-ui/react'
18
16
  *
19
17
  * function Example() {
20
18
  * return (
21
- * <Stepper.Root count={2} defaultStep={0}>
22
- * <div className="flex items-start gap-4">
23
- * <Stepper.List className="w-32 shrink-0">
24
- * <Stepper.Item index={0}>
25
- * <Stepper.OpenButton>
26
- * <Stepper.Indicator />
27
- * Details
28
- * </Stepper.OpenButton>
19
+ * <Stepper.Root items={['details', 'review']}>
20
+ * <Stepper.Container>
21
+ * <Stepper.ItemsList>
22
+ * <Stepper.Item value="details">
23
+ * <Stepper.Indicator>
24
+ * <Stepper.IndicatorIcon />
25
+ * <Stepper.IndicatorText />
26
+ * </Stepper.Indicator>
27
+ * <Stepper.ItemTitle>Details</Stepper.ItemTitle>
28
+ * <Stepper.ItemDescription>Set your basics</Stepper.ItemDescription>
29
29
  * <Stepper.Separator />
30
30
  * </Stepper.Item>
31
- * <Stepper.Item index={1}>
32
- * <Stepper.OpenButton>
33
- * <Stepper.Indicator />
34
- * Review
35
- * </Stepper.OpenButton>
31
+ * <Stepper.Item value="review">
32
+ * <Stepper.Indicator>
33
+ * <Stepper.IndicatorIcon />
34
+ * <Stepper.IndicatorText />
35
+ * </Stepper.Indicator>
36
+ * <Stepper.ItemTitle>Review</Stepper.ItemTitle>
37
+ * <Stepper.ItemDescription>Check everything</Stepper.ItemDescription>
38
+ * <Stepper.Separator />
36
39
  * </Stepper.Item>
37
- * </Stepper.List>
38
- * <div className="w-full flex flex-col gap-3">
39
- * <Stepper.Content index={0}>Set your basics.</Stepper.Content>
40
- * <Stepper.Content index={1}>Check everything.</Stepper.Content>
41
- * <Stepper.CompletedContent>Done.</Stepper.CompletedContent>
42
- * <div className="flex gap-2">
43
- * <Stepper.PreviousButton>Previous</Stepper.PreviousButton>
40
+ * </Stepper.ItemsList>
41
+ * <Stepper.Content value="details">
42
+ * <Stepper.Body>Set your basics.</Stepper.Body>
43
+ * <Stepper.Footer>
44
+ * <Stepper.NextButton asChild>
45
+ * <Button.default variant="primary" size="small">
46
+ * Continue
47
+ * </Button.default>
48
+ * </Stepper.NextButton>
49
+ * </Stepper.Footer>
50
+ * </Stepper.Content>
51
+ * <Stepper.Content value="review">
52
+ * <Stepper.Body>Check everything.</Stepper.Body>
53
+ * <Stepper.Footer>
54
+ * <Stepper.PreviousButton>Back</Stepper.PreviousButton>
44
55
  * <Stepper.NextButton asChild>
45
56
  * <Button.default variant="primary" size="small">
46
- * Next
57
+ * Place order
47
58
  * </Button.default>
48
59
  * </Stepper.NextButton>
49
- * </div>
50
- * </div>
51
- * </div>
60
+ * </Stepper.Footer>
61
+ * </Stepper.Content>
62
+ * <Stepper.CompletedContent>Done.</Stepper.CompletedContent>
63
+ * </Stepper.Container>
52
64
  * </Stepper.Root>
53
65
  * )
54
66
  * }
55
67
  *
56
- * @example Show the steps horizontally
57
- * // Keep the same indexed Item and Content pairs.
58
- * ;<Stepper.Root count={2} orientation="horizontal">
68
+ * @example Control the step
69
+ * const [step, setStep] = useState<string | null>('details')
70
+ *
71
+ * ;<Stepper.Root items={['details', 'review']} value={step} onValueChange={setStep}>
59
72
  * {
60
73
  * // Rest of components here
61
74
  * }
62
75
  * </Stepper.Root>
63
76
  */
64
- export function Root({ children, className, orientation = 'vertical', ...props }) {
65
- // className is left out, since Root renders no element of its own to receive it
66
- return (_jsx(Steps.Root, { ...props, orientation: orientation, asChild: true, children: _jsx(_Fragment, { children: children }) }));
77
+ export function Root({ items, value, defaultValue = items[0], onValueChange, preventStepClicks = false, children, }) {
78
+ if (new Set(items).size !== items.length)
79
+ throw new Error('Stepper.Root items must not repeat a value.');
80
+ // Ark counts steps by index, with one past the last meaning every step is done
81
+ const toStep = (stepValue) => (stepValue === null ? items.length : indexOf(items, stepValue, 'Root'));
82
+ return (_jsx(StepperContext.Provider, { value: { items, preventStepClicks }, children: _jsx(Steps.Root, { count: items.length, step: value === undefined ? undefined : toStep(value), defaultStep: toStep(defaultValue ?? null), onStepChange: onValueChange && ((details) => onValueChange(details.step === items.length ? null : items[details.step])), orientation: "vertical", linear: preventStepClicks, asChild: true, children: _jsx(_Fragment, { children: children }) }) }));
67
83
  }
68
- /** Moves to the next step. Pass asChild to give this behavior to your own button, like a "Continue" Button; it keeps the state data attributes but none of this part's styling. */
69
- export function NextButton({ asChild, disableStyle, ...props }) {
70
- const styleAttributes = asChildStyleAttributes({ asChild, component: 'stepper-nextbutton', disableStyle });
71
- return _jsx(Steps.NextTrigger, { ...props, asChild: asChild, ...styleAttributes });
84
+ /** Lays out ItemsList on the left of the current Content or CompletedContent. Spacing only, no look of its own. */
85
+ export function Container({ disableStyle, className, ref, ...props }) {
86
+ useStepperContext('Container');
87
+ return (_jsx("div", { ...props, ref: ref, "data-component": "stepper-container", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
72
88
  }
73
- /** Moves to the previous step. Pass asChild to give this behavior to your own button, like a "Back" Button; it keeps the state data attributes but none of this part's styling. */
74
- export function PreviousButton({ asChild, disableStyle, ...props }) {
75
- const styleAttributes = asChildStyleAttributes({ asChild, component: 'stepper-previousbutton', disableStyle });
76
- return _jsx(Steps.PrevTrigger, { ...props, asChild: asChild, ...styleAttributes });
89
+ /** Holds the Items; screen readers hear it as a tab list, no setup needed. */
90
+ export function ItemsList({ disableStyle, className, ...props }) {
91
+ return _jsx(Steps.List, { ...props, "data-component": "stepper-itemslist", "data-style-disabled": disableStyle ? '' : undefined, className: className });
77
92
  }
78
- export function List({ disableStyle, className, ...props }) {
79
- return (_jsx(Steps.List, { "data-component": "stepper-list", "data-style-disabled": disableStyle ? '' : undefined, className: className, ...props }));
93
+ /**
94
+ * One step, as a single button opening its step on click, no click handler needed; does nothing with preventStepClicks.
95
+ * Carries data-current, data-complete or data-incomplete. Its value must be one of Root's items.
96
+ */
97
+ export function Item({ value, disableStyle, className, style, ...props }) {
98
+ const context = useStepperContext('Item');
99
+ // Ark splits a step into an Item and its Trigger; asChild merges them into one button
100
+ return (_jsx(Steps.Item, { index: indexOf(context.items, value, 'Item'), asChild: true, children: _jsx(Steps.Trigger, { ...props, "data-component": "stepper-item", "data-style-disabled": disableStyle ? '' : undefined, "data-prevent-clicks": context.preventStepClicks ? '' : undefined, className: className,
101
+ // Core: the Separator is positioned against it
102
+ style: { position: 'relative', ...style } }) }));
80
103
  }
81
- export function Item({ disableStyle, className, ...props }) {
82
- return (_jsx(Steps.Item, { "data-component": "stepper-item", "data-style-disabled": disableStyle ? '' : undefined, className: className, ...props }));
104
+ /** The circle marking the step, always shown. Holds IndicatorIcon and IndicatorText, which take turns automatically. */
105
+ export function Indicator({ disableStyle, className, ref, ...props }) {
106
+ // A span, since it sits inside Item's button
107
+ return (_jsx(Steps.Indicator, { asChild: true, children: _jsx("span", { ...props, ref: ref, "data-component": "stepper-indicator", "data-style-disabled": disableStyle ? '' : undefined, className: className }) }));
83
108
  }
84
- /** 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. */
85
- export function Indicator({ disableStyle, className, ...props }) {
86
- return (_jsx(Steps.ItemContext, { children: (step) => (_jsx(Steps.Indicator, { "data-component": "stepper-indicator", "data-style-disabled": disableStyle ? '' : undefined, "data-completed": step.completed ? '' : undefined, className: className, ...props, children: step.completed ? (_jsx(Check, { "data-component": "stepper-indicator-1", strokeWidth: 2.5 })) : (_jsx("span", { "data-component": "stepper-indicator-2" })) })) }));
109
+ /** Built-in check icon, shown only once the step is done; renders nothing before. Place inside Indicator. Omit children for the supplied default icon; pass an icon only to replace it. Defaults to strokeWidth 2.5. */
110
+ export function IndicatorIcon({ disableStyle, className, strokeWidth, children, ...props }) {
111
+ const item = useStepsItemContext();
112
+ if (!item.completed)
113
+ return null;
114
+ return cloneElement(children ?? _jsx(Check, {}), {
115
+ 'aria-hidden': true,
116
+ ...props,
117
+ ...itemStateAttributes(item),
118
+ strokeWidth: strokeWidth ?? children?.props.strokeWidth ?? 2.5,
119
+ 'data-component': 'stepper-indicatoricon',
120
+ 'data-style-disabled': disableStyle ? '' : undefined,
121
+ className: twMerge(children?.props.className, className),
122
+ });
87
123
  }
88
- export function Separator({ disableStyle, className, ...props }) {
89
- return (_jsx(Steps.Separator, { "data-component": "stepper-separator", "data-style-disabled": disableStyle ? '' : undefined, className: className, ...props }));
124
+ /** The step's number, worked out automatically; renders nothing once the step is done, leaving room for IndicatorIcon. Pass children only to show something else. Place inside Indicator. */
125
+ export function IndicatorText({ disableStyle, className, children, ref, ...props }) {
126
+ const item = useStepsItemContext();
127
+ if (item.completed)
128
+ return null;
129
+ return (_jsx("span", { ...props, ...itemStateAttributes(item), ref: ref, "data-component": "stepper-indicatortext", "data-style-disabled": disableStyle ? '' : undefined, className: className, children: children ?? item.index + 1 }));
90
130
  }
91
- export function Content({ disableStyle, className, ...props }) {
92
- return (_jsx(Steps.Content, { "data-component": "stepper-content", "data-style-disabled": disableStyle ? '' : undefined, className: className, ...props }));
131
+ /**
132
+ * Optional small text above ItemTitle, like a date ("Jan 2024") or "Step 1 of 3". Add it only when the steps need that extra context; leave it out otherwise.
133
+ */
134
+ export function ItemMeta({ disableStyle, className, ref, ...props }) {
135
+ const item = useStepsItemContext();
136
+ return (_jsx("span", { ...props, ...itemStateAttributes(item), ref: ref, "data-component": "stepper-itemmeta", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
93
137
  }
138
+ /** The step's name, beside the Indicator. */
139
+ export function ItemTitle({ disableStyle, className, ref, ...props }) {
140
+ const item = useStepsItemContext();
141
+ return (_jsx("span", { ...props, ...itemStateAttributes(item), ref: ref, "data-component": "stepper-itemtitle", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
142
+ }
143
+ /** A short hint under ItemTitle, like "Select product". */
144
+ export function ItemDescription({ disableStyle, className, ref, ...props }) {
145
+ const item = useStepsItemContext();
146
+ return (_jsx("span", { ...props, ...itemStateAttributes(item), ref: ref, "data-component": "stepper-itemdescription", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
147
+ }
148
+ /**
149
+ * The line joining a step to the next one, filled once the step is done. Renders nothing on the last step, so every Item can hold one.
150
+ *
151
+ * Changing its gap from the Indicators: the line lives inside its own Item, stretching between two distances measured from that Item's edges.
152
+ * - top: where the line starts, measured from the top of this Item. It must clear this step's Indicator, so it's the Indicator's height plus the gap you want under it.
153
+ * The preset's top-8.5 (34px) is the 26px Indicator + an 8px gap.
154
+ * - bottom: where the line ends, measured from the bottom of this Item. The next step's Indicator starts right at that edge, so bottom is exactly the gap above the next Indicator.
155
+ * The preset's bottom-1 is a 4px gap.
156
+ * Together they set the line's length: the Item's height minus top minus bottom. A taller Item, like one with a longer description, just makes a longer line with the same gaps.
157
+ * If you resize the Indicator, set top to its new height plus your gap, and left to half its width, so the line stays centered under it.
158
+ *
159
+ * @example Even 8px gaps on both ends
160
+ * // 26px Indicator + 8px gap = top-8.5; 8px gap = bottom-2
161
+ * ;<Stepper.Separator className="top-8.5 bottom-2" />
162
+ *
163
+ * @example A line touching both Indicators
164
+ * // top equals the Indicator's height; bottom is 0
165
+ * ;<Stepper.Separator className="top-6.5 bottom-0" />
166
+ */
167
+ export function Separator({ disableStyle, className, style, ref, ...props }) {
168
+ const item = useStepsItemContext();
169
+ if (item.last)
170
+ return null;
171
+ // A span, since it sits inside Item's button
172
+ return (_jsx(Steps.Separator, { asChild: true, children: _jsx("span", { ...props, ref: ref, "aria-hidden": true, "data-component": "stepper-separator", "data-style-disabled": disableStyle ? '' : undefined, className: className,
173
+ // Core: drawn over the Item; the preset places it
174
+ style: { position: 'absolute', ...style } }) }));
175
+ }
176
+ /** Holds the step's main content, like its form fields. Place inside a Content, above its Footer. Spacing only, no look of its own. */
177
+ export function Body({ disableStyle, className, ref, ...props }) {
178
+ useStepperContext('Body');
179
+ return (_jsx("div", { ...props, ref: ref, "data-component": "stepper-body", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
180
+ }
181
+ /** One step's side, beside ItemsList, showing only while its step is current, no visibility condition needed. Holds the step's Body and Footer. Its value must be one of Root's items. */
182
+ export function Content({ value, disableStyle, className, ...props }) {
183
+ const context = useStepperContext('Content');
184
+ return (_jsx(Steps.Content, { ...props, index: indexOf(context.items, value, 'Content'), "data-component": "stepper-content", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
185
+ }
186
+ /** Shows only once every step is done, after NextButton on the last step. */
94
187
  export function CompletedContent({ disableStyle, className, ...props }) {
95
- return (_jsx(Steps.CompletedContent, { "data-component": "stepper-completedcontent", "data-style-disabled": disableStyle ? '' : undefined, className: className, ...props }));
188
+ return _jsx(Steps.CompletedContent, { ...props, "data-component": "stepper-completedcontent", "data-style-disabled": disableStyle ? '' : undefined, className: className });
189
+ }
190
+ /** The row under the step's Body, inside its Content, holding that step's PreviousButton and NextButton directly. Leave it out of a step needing no buttons. */
191
+ export function Footer({ disableStyle, className, ref, ...props }) {
192
+ useStepperContext('Footer');
193
+ return (_jsx("div", { ...props, ref: ref, "data-component": "stepper-footer", "data-style-disabled": disableStyle ? '' : undefined, className: className }));
194
+ }
195
+ /** Moves to the previous step, no click handler needed. Place it in a Content's Footer, never outside a Content; leave it out of the first step's Footer. Pass asChild to give this behavior to your own button, like a "Back" Button; it keeps the state data attributes but none of this part's styling. */
196
+ export function PreviousButton({ asChild, disableStyle, ...props }) {
197
+ return _jsx(Steps.PrevTrigger, { ...props, asChild: asChild, ...asChildStyleAttributes({ asChild, component: 'stepper-previousbutton', disableStyle }) });
198
+ }
199
+ /** Moves to the next step, no click handler needed; on the last step, it finishes them all. Place it in a Content's Footer, never outside a Content, worded for that step, like "I agree". Pass asChild to give this behavior to your own button, like a primary "Continue" Button; it keeps the state data attributes but none of this part's styling. */
200
+ export function NextButton({ asChild, disableStyle, ...props }) {
201
+ return _jsx(Steps.NextTrigger, { ...props, asChild: asChild, ...asChildStyleAttributes({ asChild, component: 'stepper-nextbutton', disableStyle }) });
202
+ }
203
+ function useStepperContext(part) {
204
+ const context = useContext(StepperContext);
205
+ if (!context)
206
+ throw new Error(`Stepper.${part} must be placed inside Stepper.Root.`);
207
+ return context;
208
+ }
209
+ function indexOf(items, value, part) {
210
+ const index = items.indexOf(value);
211
+ if (index === -1)
212
+ throw new Error(`Stepper.${part} got the value "${value}", which is missing from Stepper.Root items.`);
213
+ return index;
96
214
  }
97
- /** Opens its Item's step. Pass asChild to give this behavior to your own button; it keeps the state data attributes but none of this part's styling. */
98
- export function OpenButton({ asChild, disableStyle, ...props }) {
99
- const styleAttributes = asChildStyleAttributes({ asChild, component: 'stepper-openbutton', disableStyle });
100
- return _jsx(Steps.Trigger, { ...props, asChild: asChild, ...styleAttributes });
215
+ // The same step-state attributes Ark puts on its own parts, for the parts rendered here
216
+ function itemStateAttributes(item) {
217
+ return {
218
+ 'data-current': item.current ? '' : undefined,
219
+ 'data-complete': item.completed ? '' : undefined,
220
+ 'data-incomplete': item.incomplete ? '' : undefined,
221
+ };
101
222
  }
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export * as FileUpload from './components/fileupload/index.tsx';
11
11
  export * as Input from './components/input/index.tsx';
12
12
  export * as Menu from './components/menu/index.tsx';
13
13
  export * as NumberInput from './components/numberinput/index.tsx';
14
+ export * as Pagination from './components/pagination/index.tsx';
14
15
  export * as PasswordInput from './components/passwordinput/index.tsx';
15
16
  export * as OtpInput from './components/otpinput/index.tsx';
16
17
  export * as Popover from './components/popover/index.tsx';
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ export * as FileUpload from "./components/fileupload/index.js";
11
11
  export * as Input from "./components/input/index.js";
12
12
  export * as Menu from "./components/menu/index.js";
13
13
  export * as NumberInput from "./components/numberinput/index.js";
14
+ export * as Pagination from "./components/pagination/index.js";
14
15
  export * as PasswordInput from "./components/passwordinput/index.js";
15
16
  export * as OtpInput from "./components/otpinput/index.js";
16
17
  export * as Popover from "./components/popover/index.js";
@@ -15,6 +15,14 @@ export declare const ChevronDown: {
15
15
  ({ size, width, height, color, strokeWidth, absoluteStrokeWidth, nonScalingStroke, className, children, ref, ...props }: SVGIconProps): import("react").JSX.Element;
16
16
  displayName: string;
17
17
  };
18
+ export declare const ChevronFirst: {
19
+ ({ size, width, height, color, strokeWidth, absoluteStrokeWidth, nonScalingStroke, className, children, ref, ...props }: SVGIconProps): import("react").JSX.Element;
20
+ displayName: string;
21
+ };
22
+ export declare const ChevronLast: {
23
+ ({ size, width, height, color, strokeWidth, absoluteStrokeWidth, nonScalingStroke, className, children, ref, ...props }: SVGIconProps): import("react").JSX.Element;
24
+ displayName: string;
25
+ };
18
26
  export declare const ChevronLeft: {
19
27
  ({ size, width, height, color, strokeWidth, absoluteStrokeWidth, nonScalingStroke, className, children, ref, ...props }: SVGIconProps): import("react").JSX.Element;
20
28
  displayName: string;
@@ -39,6 +47,10 @@ export declare const CircleCheck: {
39
47
  ({ size, width, height, color, strokeWidth, absoluteStrokeWidth, nonScalingStroke, className, children, ref, ...props }: SVGIconProps): import("react").JSX.Element;
40
48
  displayName: string;
41
49
  };
50
+ export declare const Ellipsis: {
51
+ ({ size, width, height, color, strokeWidth, absoluteStrokeWidth, nonScalingStroke, className, children, ref, ...props }: SVGIconProps): import("react").JSX.Element;
52
+ displayName: string;
53
+ };
42
54
  export declare const Eye: {
43
55
  ({ size, width, height, color, strokeWidth, absoluteStrokeWidth, nonScalingStroke, className, children, ref, ...props }: SVGIconProps): import("react").JSX.Element;
44
56
  displayName: string;
@@ -43,6 +43,14 @@ export const Check = /* @__PURE__ */ createIcon('check', [
43
43
  export const ChevronDown = /* @__PURE__ */ createIcon('chevron-down', [
44
44
  ['path', { d: 'm6 9 6 6 6-6' }],
45
45
  ]);
46
+ export const ChevronFirst = /* @__PURE__ */ createIcon('chevron-first', [
47
+ ['path', { d: 'm17 18-6-6 6-6' }],
48
+ ['path', { d: 'M7 6v12' }],
49
+ ]);
50
+ export const ChevronLast = /* @__PURE__ */ createIcon('chevron-last', [
51
+ ['path', { d: 'm7 18 6-6-6-6' }],
52
+ ['path', { d: 'M17 6v12' }],
53
+ ]);
46
54
  export const ChevronLeft = /* @__PURE__ */ createIcon('chevron-left', [
47
55
  ['path', { d: 'm15 18-6-6 6-6' }],
48
56
  ]);
@@ -65,6 +73,11 @@ export const CircleCheck = /* @__PURE__ */ createIcon('circle-check', [
65
73
  ['circle', { cx: '12', cy: '12', r: '10' }],
66
74
  ['path', { d: 'm16 9-5.5 5.5L8 12' }],
67
75
  ]);
76
+ export const Ellipsis = /* @__PURE__ */ createIcon('ellipsis', [
77
+ ['circle', { cx: '12', cy: '12', r: '1' }],
78
+ ['circle', { cx: '19', cy: '12', r: '1' }],
79
+ ['circle', { cx: '5', cy: '12', r: '1' }],
80
+ ]);
68
81
  export const Eye = /* @__PURE__ */ createIcon('eye', [
69
82
  [
70
83
  'path',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@central-ui/react",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Accessible React components with optional Tailwind CSS style presets",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -0,0 +1,55 @@
1
+ /* pagination */
2
+ @layer components {
3
+ :where(:not([data-style-disabled])) {
4
+ &:where([data-component='pagination-container']),
5
+ &:where([data-component='pagination-pagelist']) {
6
+ @apply flex w-fit items-center gap-1;
7
+ }
8
+
9
+ &:where([data-component='pagination-firstbutton']),
10
+ &:where([data-component='pagination-previousbutton']),
11
+ &:where([data-component='pagination-nextbutton']),
12
+ &:where([data-component='pagination-lastbutton']),
13
+ &:where([data-component='pagination-page'])[data-type='page'] {
14
+ @apply flex h-7 min-w-7 shrink-0 cursor-pointer items-center justify-center rounded-full px-2 text-[0.8rem] font-medium tabular-nums outline-none select-none focus-visible:outline-2 focus-visible:outline-blue-500 enabled:active:scale-97 transition-[background-color,color,transform] duration-400 ease-[cubic-bezier(0.2,0.8,0,1)] motion-reduce:transition-none disabled:pointer-events-none disabled:cursor-default;
15
+
16
+ &:not(:disabled) {
17
+ @apply bg-neutral-200 text-neutral-950 hover:bg-neutral-300 focus-visible:bg-neutral-300 active:bg-neutral-300;
18
+ }
19
+ &:disabled {
20
+ @apply bg-black/3 text-neutral-400;
21
+ }
22
+ &[data-selected] {
23
+ @apply bg-neutral-950 text-white hover:bg-black focus-visible:bg-black active:bg-black;
24
+ }
25
+ }
26
+
27
+ &:where([data-component='pagination-firstbutton']),
28
+ &:where([data-component='pagination-previousbutton']),
29
+ &:where([data-component='pagination-nextbutton']),
30
+ &:where([data-component='pagination-lastbutton']) {
31
+ /* Roomy padding for text; the icon's negative margins below pull an icon-only button back to an exact square */
32
+ @apply gap-2.5 px-3;
33
+ }
34
+
35
+ &:where([data-component='pagination-page'])[data-type='ellipsis'] {
36
+ @apply flex h-7 min-w-7 shrink-0 items-center justify-center text-neutral-500;
37
+ }
38
+
39
+ &:where([data-component='pagination-firstbuttonicon']),
40
+ &:where([data-component='pagination-previousbuttonicon']),
41
+ &:where([data-component='pagination-nextbuttonicon']),
42
+ &:where([data-component='pagination-lastbuttonicon']),
43
+ &:where([data-component='pagination-pageicon']) {
44
+ @apply h-4 w-fit shrink-0;
45
+ }
46
+
47
+ /* Arrows point the reading direction, and their negative margins keep icon-only buttons square */
48
+ &:where([data-component='pagination-firstbuttonicon']),
49
+ &:where([data-component='pagination-previousbuttonicon']),
50
+ &:where([data-component='pagination-nextbuttonicon']),
51
+ &:where([data-component='pagination-lastbuttonicon']) {
52
+ @apply -mx-1.5 rtl:-scale-x-100;
53
+ }
54
+ }
55
+ }
@@ -1,55 +1,91 @@
1
1
  /* stepper */
2
2
  @layer components {
3
3
  :where(:not([data-style-disabled])) {
4
- &:where([data-component='stepper-list']) {
5
- @apply flex w-full flex-col gap-1.5;
6
- &[data-orientation='horizontal'] {
7
- @apply flex-row;
8
- }
4
+ /* Steps on the left of the current Content */
5
+ &:where([data-component='stepper-container']) {
6
+ @apply flex w-full items-start gap-8;
7
+ }
8
+
9
+ &:where([data-component='stepper-itemslist']) {
10
+ @apply flex w-56 shrink-0 flex-col;
11
+ }
12
+
13
+ &:where([data-component='stepper-body']) {
14
+ @apply flex min-w-0 flex-col gap-3;
9
15
  }
10
16
 
17
+ &:where([data-component='stepper-footer']) {
18
+ @apply flex items-center gap-2;
19
+ }
20
+
21
+ /* Indicator on the left, spanning the meta, title and description stacked beside it */
11
22
  &:where([data-component='stepper-item']) {
12
- @apply relative flex w-full items-stretch gap-3;
13
- &[data-orientation='horizontal'] {
14
- @apply flex-1;
23
+ @apply grid w-full cursor-pointer grid-cols-[auto_1fr] content-start gap-x-3 pb-7 text-left outline-none last:pb-0 focus-visible:rounded-lg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500;
24
+ /* Steps move only with Previous and Next */
25
+ &[data-prevent-clicks] {
26
+ @apply cursor-default;
15
27
  }
16
28
  }
17
29
 
18
30
  &:where([data-component='stepper-indicator']) {
19
- @apply flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-neutral-200;
31
+ @apply row-span-3 flex aspect-square size-6.5 shrink-0 items-center justify-center rounded-full text-xs font-medium tabular-nums transition-colors duration-200;
32
+ &[data-incomplete] {
33
+ @apply bg-neutral-200 text-neutral-500;
34
+ }
35
+ &[data-current] {
36
+ @apply bg-blue-600 text-white ring-4 ring-blue-200;
37
+ }
38
+ &[data-complete] {
39
+ @apply bg-neutral-950 text-white;
40
+ }
20
41
  }
21
42
 
22
- &:where([data-component='stepper-indicator'])[data-completed] {
23
- @apply bg-neutral-950;
43
+ &:where([data-component='stepper-indicatoricon']) {
44
+ @apply size-3.5 shrink-0;
24
45
  }
25
46
 
26
- &:where([data-component='stepper-indicator'])
27
- &:where([data-component='stepper-indicator-1']) {
28
- @apply h-3.5 w-fit shrink-0 text-white;
47
+ /* Optional small text above the title, centered on the Indicator */
48
+ &:where([data-component='stepper-itemmeta']) {
49
+ @apply col-start-2 text-xs text-neutral-600 ;
50
+ &[data-incomplete] {
51
+ @apply text-neutral-400;
52
+ }
29
53
  }
30
54
 
31
- &:where([data-component='stepper-indicator'])
32
- &:where([data-component='stepper-indicator-2']) {
33
- @apply h-2 w-2 rounded-full bg-white;
55
+ &:where([data-component='stepper-itemtitle']) {
56
+ @apply col-start-2 text-[0.9rem] -mt-0.5 font-medium text-neutral-950 truncate;
57
+ /* Centered on the Indicator when no ItemMeta comes first */
58
+
59
+ &[data-incomplete] {
60
+ @apply text-neutral-400;
61
+ }
34
62
  }
35
63
 
64
+ &:where([data-component='stepper-itemdescription']) {
65
+ @apply col-start-2 text-sm text-neutral-600;
66
+ &[data-incomplete] {
67
+ @apply text-neutral-400;
68
+ }
69
+ }
70
+
71
+ /* The line from this step's Indicator down to the next one; top and bottom set its gaps from both Indicators */
36
72
  &:where([data-component='stepper-separator']) {
37
- @apply absolute top-7 bottom-0 left-3 w-px bg-neutral-300 data-[complete]:bg-neutral-700;
38
- &[data-orientation='horizontal'] {
39
- @apply top-3 right-0 bottom-auto left-7 h-px w-auto;
73
+ @apply top-8.5 bottom-1 left-3.25 w-px -translate-x-1/2 bg-neutral-300;
74
+ &[data-complete] {
75
+ @apply bg-neutral-950;
40
76
  }
41
77
  }
42
78
 
79
+ /* Body above Footer; only while shown, since its hidden attribute must keep hiding it */
43
80
  &:where([data-component='stepper-content']) {
44
- @apply w-full p-3;
81
+ @apply min-w-0 flex-1;
82
+ &:not([hidden]) {
83
+ @apply flex flex-col gap-4;
84
+ }
45
85
  }
46
86
 
47
87
  &:where([data-component='stepper-completedcontent']) {
48
- @apply w-full rounded-xl bg-green-50 p-3 text-sm leading-relaxed text-green-800;
49
- }
50
-
51
- &:where([data-component='stepper-openbutton']) {
52
- @apply flex w-full cursor-pointer items-start gap-3 pb-5 text-left text-[0.95rem] font-medium text-neutral-950 outline-none focus-visible:rounded-lg focus-visible:outline-2 focus-visible:outline-blue-500 disabled:pointer-events-none disabled:opacity-30;
88
+ @apply min-w-0 flex-1 rounded-xl bg-green-50 p-3 text-sm text-green-800;
53
89
  }
54
90
  }
55
91
  }
@@ -58,7 +94,7 @@
58
94
  @layer components {
59
95
  :where(:not([data-style-disabled])) {
60
96
  &:where([data-component='stepper-nextbutton'], [data-component='stepper-previousbutton']) {
61
- @apply relative flex w-fit cursor-pointer items-center justify-center gap-1.5 rounded-full font-medium text-nowrap select-none outline-none focus-visible:outline-2 focus-visible:outline-blue-500 enabled:active:scale-97 transition-[background-color,box-shadow,transform] duration-400 ease-[cubic-bezier(0.2,0.8,0,1)] motion-reduce:transition-none disabled:pointer-events-none disabled:cursor-default min-h-7 px-4 py-1 text-[0.8rem] leading-4;
97
+ @apply relative flex w-fit cursor-pointer items-center justify-center gap-1.5 rounded-full font-medium text-nowrap select-none outline-none focus-visible:outline-2 focus-visible:outline-blue-500 enabled:active:scale-97 transition-[background-color,box-shadow,transform] duration-400 ease-[cubic-bezier(0.2,0.8,0,1)] motion-reduce:transition-none disabled:pointer-events-none disabled:cursor-default min-h-7 px-4 py-1 text-[0.8rem];
62
98
 
63
99
  &:not(:disabled) {
64
100
  @apply bg-neutral-200 text-neutral-950 hover:bg-neutral-300 focus-visible:bg-neutral-300 active:bg-neutral-300 aria-expanded:bg-neutral-300;
@@ -35,6 +35,7 @@
35
35
  @import './shopify-polaris-table.css';
36
36
  @import './shopify-polaris-card.css';
37
37
  @import './shopify-polaris-carousel.css';
38
+ @import './shopify-polaris-pagination.css';
38
39
  @import './shopify-polaris-skeleton.css';
39
40
  @import './shopify-polaris-avatar.css';
40
41
  @import './shopify-polaris-metric.css';
@@ -0,0 +1,49 @@
1
+ /* pagination */
2
+ @layer components {
3
+ :where(:not([data-style-disabled])) {
4
+ &:where([data-component='pagination-container']),
5
+ &:where([data-component='pagination-pagelist']) {
6
+ @apply flex w-fit items-center gap-1;
7
+ }
8
+
9
+ &:where([data-component='pagination-firstbutton']),
10
+ &:where([data-component='pagination-previousbutton']),
11
+ &:where([data-component='pagination-nextbutton']),
12
+ &:where([data-component='pagination-lastbutton']),
13
+ &:where([data-component='pagination-page'])[data-type='page'] {
14
+ @apply flex h-7.5 min-w-7.5 shrink-0 cursor-pointer items-center justify-center rounded-full px-2.5 bg-white text-[0.8rem] font-semibold tabular-nums text-neutral-700 outline-none select-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;
15
+
16
+ &[data-selected] {
17
+ @apply [background:linear-gradient(180deg,rgba(48,48,48,0)_63.53%,rgba(255,255,255,0.15)_100%),rgba(48,48,48,1)] text-white shadow-[0_-1px_0_1px_rgba(0,0,0,0.8)_inset,0_0_0_1px_rgba(48,48,48,1)_inset,0_0.5px_0_1.5px_rgba(255,255,255,0.25)_inset] hover:brightness-85 active:shadow-[0_0_0_1px_#000000_inset,0px_0px_0px_50px_rgba(0,0,0,0.5)_inset];
18
+ }
19
+ }
20
+
21
+ &:where([data-component='pagination-firstbutton']),
22
+ &:where([data-component='pagination-previousbutton']),
23
+ &:where([data-component='pagination-nextbutton']),
24
+ &:where([data-component='pagination-lastbutton']) {
25
+ /* Roomy padding for text; the icon's negative margins below pull an icon-only button back to an exact square */
26
+ @apply gap-2.25 px-3;
27
+ }
28
+
29
+ &:where([data-component='pagination-page'])[data-type='ellipsis'] {
30
+ @apply flex h-7.5 min-w-7.5 shrink-0 items-center justify-center text-neutral-500;
31
+ }
32
+
33
+ &:where([data-component='pagination-firstbuttonicon']),
34
+ &:where([data-component='pagination-previousbuttonicon']),
35
+ &:where([data-component='pagination-nextbuttonicon']),
36
+ &:where([data-component='pagination-lastbuttonicon']),
37
+ &:where([data-component='pagination-pageicon']) {
38
+ @apply h-4 w-fit shrink-0;
39
+ }
40
+
41
+ /* Arrows point the reading direction, and their negative margins keep icon-only buttons square */
42
+ &:where([data-component='pagination-firstbuttonicon']),
43
+ &:where([data-component='pagination-previousbuttonicon']),
44
+ &:where([data-component='pagination-nextbuttonicon']),
45
+ &:where([data-component='pagination-lastbuttonicon']) {
46
+ @apply -mx-1.25 rtl:-scale-x-100;
47
+ }
48
+ }
49
+ }