@mahiraltinkaya/me-ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -8
- package/package.json +1 -1
- package/registry/components/stepper-provider.tsx +0 -7
- package/registry/components/stepper-view.tsx +0 -10
- package/registry/components/ui/field-hint.tsx +0 -17
- package/registry/components/ui/input.tsx +0 -2
- package/registry/components/ui/stepper/index.tsx +0 -2
- package/registry/components/ui/stepper/step-divider.tsx +13 -12
- package/registry/components/ui/stepper/step-indicator.tsx +1 -1
- package/registry/components/ui/stepper/step-label.tsx +16 -4
- package/registry/components/ui/stepper/stepper-rail.tsx +19 -6
- package/registry.json +0 -10
- package/registry/assets/breadcrumb_arrow.svg +0 -3
- package/registry/components/ui/stepper/stepper-progress.tsx +0 -40
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ cannot cost you local changes.
|
|
|
38
38
|
| `select` | Dropdown over a string list, matching `input`'s affordances. |
|
|
39
39
|
| `button` | Button with `cva` variant and size sets. |
|
|
40
40
|
| `tooltip` | Tooltip provider, root, trigger and content. |
|
|
41
|
-
| `stepper` | Progress rail for multi-step flows.
|
|
41
|
+
| `stepper` | Progress rail for multi-step flows. |
|
|
42
42
|
| `stepper-provider` | Step state, next/back guards and the `useStepper` hook. |
|
|
43
43
|
| `step-field` | Label, control and validation message. |
|
|
44
44
|
| `form-fields` | `FormInput` and `FormSelect`, bound to react-hook-form. |
|
|
@@ -55,12 +55,11 @@ A `components.json` is honoured when the project has one, so files follow the
|
|
|
55
55
|
aliases the rest of its UI already uses. Otherwise the CLI reads `tsconfig.json`
|
|
56
56
|
paths, falling back to the `src/` convention:
|
|
57
57
|
|
|
58
|
-
| Registry path | Default destination
|
|
59
|
-
| ----------------- |
|
|
60
|
-
| `components/ui/…` | `src/components/ui/…`
|
|
61
|
-
| `components/…` | `src/components/…`
|
|
62
|
-
| `lib/…` | `src/lib/…`
|
|
63
|
-
| `assets/…` | `public/assets/images/…` |
|
|
58
|
+
| Registry path | Default destination |
|
|
59
|
+
| ----------------- | --------------------- |
|
|
60
|
+
| `components/ui/…` | `src/components/ui/…` |
|
|
61
|
+
| `components/…` | `src/components/…` |
|
|
62
|
+
| `lib/…` | `src/lib/…` |
|
|
64
63
|
|
|
65
64
|
Imports inside the copied files are rewritten to match, whatever the aliases are.
|
|
66
65
|
|
|
@@ -70,7 +69,6 @@ Imports inside the copied files are rewritten to match, whatever the aliases are
|
|
|
70
69
|
- **A shadcn base theme.** The components style against `--border`, `--input`,
|
|
71
70
|
`--muted-foreground` and friends. `add` warns about any that are missing —
|
|
72
71
|
`npx shadcn@latest init` is the quickest way to get them.
|
|
73
|
-
- **Next.js**, but only for `stepper`, which renders its divider with `next/image`.
|
|
74
72
|
|
|
75
73
|
## Publishing
|
|
76
74
|
|
package/package.json
CHANGED
|
@@ -33,13 +33,6 @@ export interface StepperProviderProps {
|
|
|
33
33
|
children: React.ReactNode;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* Step state and nothing else — no markup of its own.
|
|
38
|
-
*
|
|
39
|
-
* The rail and the active step are rendered by `<StepperView>`, which reads
|
|
40
|
-
* this context. Keeping them apart means a consumer can drive step state from
|
|
41
|
-
* a layout this component knows nothing about.
|
|
42
|
-
*/
|
|
43
36
|
function StepperProvider({
|
|
44
37
|
steps,
|
|
45
38
|
initialStep = 0,
|
|
@@ -8,22 +8,12 @@ import { cn } from "@/lib/utils";
|
|
|
8
8
|
|
|
9
9
|
export interface StepperViewProps {
|
|
10
10
|
className?: string;
|
|
11
|
-
/** One child per step, in the same order as the provider's `steps`. */
|
|
12
11
|
children: React.ReactNode;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
/**
|
|
16
|
-
* Renders the progress rail and the child sitting at the active index.
|
|
17
|
-
*
|
|
18
|
-
* The live region is what tells a screen reader the step changed: the rail is
|
|
19
|
-
* `aria-hidden` decoration and swapping the panel underneath it is otherwise a
|
|
20
|
-
* silent DOM update.
|
|
21
|
-
*/
|
|
22
14
|
function StepperView({ className, children }: StepperViewProps) {
|
|
23
15
|
const { steps, currentStep, totalSteps, goToStep } = useStepper();
|
|
24
16
|
|
|
25
|
-
// Children only change when the caller re-renders with a different set, so
|
|
26
|
-
// the array is not rebuilt on every step change.
|
|
27
17
|
const stepChildren = React.useMemo(() => React.Children.toArray(children), [children]);
|
|
28
18
|
|
|
29
19
|
return (
|
|
@@ -5,10 +5,6 @@ import { Info } from "lucide-react";
|
|
|
5
5
|
|
|
6
6
|
import { cn } from "@/lib/utils";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Solid triangle on the popup edge nearest the field. Base UI places the element
|
|
10
|
-
* along the correct edge; the rotations below point it outward per side.
|
|
11
|
-
*/
|
|
12
8
|
const ARROW_CLASS =
|
|
13
9
|
"relative block h-1.5 w-3 overflow-clip data-[side=bottom]:top-[-6px] data-[side=left]:right-[-9px] data-[side=left]:rotate-90 data-[side=right]:left-[-9px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-6px] data-[side=top]:rotate-180 before:absolute before:bottom-0 before:left-1/2 before:h-[calc(6px*sqrt(2))] before:w-[calc(6px*sqrt(2))] before:bg-ink before:content-[''] before:[transform:translate(-50%,50%)_rotate(45deg)]";
|
|
14
10
|
|
|
@@ -19,24 +15,11 @@ export interface FieldHintContent {
|
|
|
19
15
|
|
|
20
16
|
export interface FieldHintProps {
|
|
21
17
|
hint: FieldHintContent;
|
|
22
|
-
/** Driven by the field's focus state — the field is the only trigger. */
|
|
23
18
|
open: boolean;
|
|
24
|
-
/** Ties the popup to the field through `aria-describedby`. */
|
|
25
19
|
id: string;
|
|
26
20
|
className?: string;
|
|
27
21
|
}
|
|
28
22
|
|
|
29
|
-
/**
|
|
30
|
-
* Explanatory panel anchored to a field's trailing info mark.
|
|
31
|
-
*
|
|
32
|
-
* Built on Tooltip rather than Popover on purpose: a popover moves focus into
|
|
33
|
-
* itself when it opens, which would pull focus straight back out of the field
|
|
34
|
-
* that opened it. A tooltip never takes focus, and `role="tooltip"` is what this
|
|
35
|
-
* content actually is.
|
|
36
|
-
*
|
|
37
|
-
* The mark is a plain span with pointer events off, so hovering it does nothing;
|
|
38
|
-
* `open` is the single source of truth.
|
|
39
|
-
*/
|
|
40
23
|
function FieldHint({ hint, open, id, className }: FieldHintProps) {
|
|
41
24
|
return (
|
|
42
25
|
<Tooltip.Root open={open}>
|
|
@@ -28,8 +28,6 @@ function Input({
|
|
|
28
28
|
const hintId = id ? `${id}-hint` : undefined;
|
|
29
29
|
const hintOpen = Boolean(tooltip) && focused;
|
|
30
30
|
|
|
31
|
-
// Merged rather than replaced: the field can be described by both its
|
|
32
|
-
// validation message and the hint panel at the same time.
|
|
33
31
|
const describedByAll =
|
|
34
32
|
[describedBy, hintOpen ? hintId : undefined].filter(Boolean).join(" ") || undefined;
|
|
35
33
|
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import type * as React from "react";
|
|
4
4
|
|
|
5
5
|
import { cn } from "@/lib/utils";
|
|
6
|
-
import { StepperProgress } from "@/components/ui/stepper/stepper-progress";
|
|
7
6
|
import { StepperRail } from "@/components/ui/stepper/stepper-rail";
|
|
8
7
|
import type { StepperLayoutProps } from "@/components/ui/stepper/types";
|
|
9
8
|
|
|
@@ -15,7 +14,6 @@ export interface StepperProps
|
|
|
15
14
|
function Stepper({ steps, currentStep, onStepChange, className, ...props }: StepperProps) {
|
|
16
15
|
return (
|
|
17
16
|
<div className={cn("w-full", className)} {...props}>
|
|
18
|
-
<StepperProgress steps={steps} currentStep={currentStep} />
|
|
19
17
|
<StepperRail steps={steps} currentStep={currentStep} onStepChange={onStepChange} />
|
|
20
18
|
</div>
|
|
21
19
|
);
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { memo } from "react";
|
|
2
|
-
import Image from "next/image";
|
|
3
|
-
|
|
4
2
|
const StepDivider = memo(function StepDivider() {
|
|
5
3
|
return (
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
<svg
|
|
5
|
+
aria-hidden
|
|
6
|
+
viewBox="0 0 25 70"
|
|
7
|
+
preserveAspectRatio="xMidYMid meet"
|
|
8
|
+
className="block h-full w-5 shrink-0 text-zinc-300 md:w-12"
|
|
9
|
+
>
|
|
10
|
+
<path
|
|
11
|
+
d="M1 1L23.6667 35L1 69"
|
|
12
|
+
fill="none"
|
|
13
|
+
stroke="currentColor"
|
|
14
|
+
strokeLinecap="round"
|
|
15
|
+
strokeLinejoin="round"
|
|
15
16
|
/>
|
|
16
|
-
</
|
|
17
|
+
</svg>
|
|
17
18
|
);
|
|
18
19
|
});
|
|
19
20
|
|
|
@@ -21,7 +21,7 @@ const StepIndicator = memo(function StepIndicator({ status, index, icon }: StepI
|
|
|
21
21
|
<span
|
|
22
22
|
aria-hidden
|
|
23
23
|
className={cn(
|
|
24
|
-
"flex size-
|
|
24
|
+
"flex size-8 shrink-0 items-center justify-center rounded-full text-sm font-semibold transition-colors duration-200 md:size-9 md:text-[0.9375rem]",
|
|
25
25
|
indicatorStyles[status],
|
|
26
26
|
)}
|
|
27
27
|
>
|
|
@@ -5,7 +5,7 @@ import type { StepStatus } from "@/components/ui/stepper/types";
|
|
|
5
5
|
|
|
6
6
|
const labelStyles: Record<StepStatus, string> = {
|
|
7
7
|
completed: "text-zinc-900 font-semibold",
|
|
8
|
-
current: "text-zinc-900 font-semibold",
|
|
8
|
+
current: "text-brand md:text-zinc-900 font-semibold",
|
|
9
9
|
upcoming: "text-zinc-400 font-medium",
|
|
10
10
|
};
|
|
11
11
|
|
|
@@ -13,12 +13,24 @@ export interface StepLabelProps {
|
|
|
13
13
|
label: string;
|
|
14
14
|
description?: string;
|
|
15
15
|
status: StepStatus;
|
|
16
|
+
className?: string;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const StepLabel = memo(function StepLabel({
|
|
19
|
+
const StepLabel = memo(function StepLabel({
|
|
20
|
+
label,
|
|
21
|
+
description,
|
|
22
|
+
status,
|
|
23
|
+
className,
|
|
24
|
+
}: StepLabelProps) {
|
|
19
25
|
return (
|
|
20
|
-
<span
|
|
21
|
-
|
|
26
|
+
<span
|
|
27
|
+
className={cn(
|
|
28
|
+
"flex min-w-0 flex-col text-left transition-colors",
|
|
29
|
+
labelStyles[status],
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
<span className="truncate text-sm whitespace-nowrap md:text-[0.9375rem]">{label}</span>
|
|
22
34
|
{description ? (
|
|
23
35
|
<span className="truncate text-xs whitespace-nowrap opacity-70">{description}</span>
|
|
24
36
|
) : null}
|
|
@@ -28,7 +28,7 @@ function StepperRailItem({ step, index, currentStep, onStepChange }: StepperRail
|
|
|
28
28
|
const handleClick = useCallback(() => onStepChange?.(index), [onStepChange, index]);
|
|
29
29
|
|
|
30
30
|
const className = cn(
|
|
31
|
-
"flex w-full items-center gap-
|
|
31
|
+
"flex w-full items-center gap-2 rounded-full px-2 py-2.5 transition-colors duration-200 md:gap-4 md:px-2.5",
|
|
32
32
|
isCurrent && "bg-white",
|
|
33
33
|
clickable &&
|
|
34
34
|
"cursor-pointer hover:bg-white/60 focus-visible:outline-brand focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
@@ -37,7 +37,15 @@ function StepperRailItem({ step, index, currentStep, onStepChange }: StepperRail
|
|
|
37
37
|
const content = (
|
|
38
38
|
<>
|
|
39
39
|
<StepIndicator status={status} index={index} icon={step.icon} />
|
|
40
|
-
<StepLabel
|
|
40
|
+
<StepLabel
|
|
41
|
+
label={step.label}
|
|
42
|
+
description={step.description}
|
|
43
|
+
status={status}
|
|
44
|
+
// Below `md` only the active step is legible enough to be worth the
|
|
45
|
+
// width. `sr-only` rather than `hidden`, so the other steps keep their
|
|
46
|
+
// names in the accessibility tree instead of becoming bare numbers.
|
|
47
|
+
className={cn(!isCurrent && "sr-only md:not-sr-only md:flex")}
|
|
48
|
+
/>
|
|
41
49
|
</>
|
|
42
50
|
);
|
|
43
51
|
|
|
@@ -76,15 +84,20 @@ const StepperRail = memo(function StepperRail({
|
|
|
76
84
|
onStepChange,
|
|
77
85
|
}: StepperLayoutProps) {
|
|
78
86
|
return (
|
|
79
|
-
<nav aria-label="İlerleme" data-slot="stepper" className="relative
|
|
87
|
+
<nav aria-label="İlerleme" data-slot="stepper" className="relative block">
|
|
80
88
|
<span
|
|
81
89
|
aria-hidden
|
|
82
90
|
className="pointer-events-none absolute top-1/2 left-1/2 h-px w-screen -translate-x-1/2 -translate-y-1/2 bg-zinc-200/80"
|
|
83
91
|
/>
|
|
84
|
-
<ol className="relative flex w-full items-stretch rounded-full border border-zinc-200/90 bg-white px-0.75">
|
|
92
|
+
<ol className="relative flex h-17 w-full items-stretch rounded-full border border-zinc-200/90 bg-white px-0.75">
|
|
85
93
|
{steps.map((step, index) => (
|
|
86
94
|
<Fragment key={step.label}>
|
|
87
|
-
<li
|
|
95
|
+
<li
|
|
96
|
+
className={cn(
|
|
97
|
+
"flex min-w-0 md:flex-1",
|
|
98
|
+
index === currentStep ? "flex-1" : "flex-none",
|
|
99
|
+
)}
|
|
100
|
+
>
|
|
88
101
|
<StepperRailItem
|
|
89
102
|
step={step}
|
|
90
103
|
index={index}
|
|
@@ -93,7 +106,7 @@ const StepperRail = memo(function StepperRail({
|
|
|
93
106
|
/>
|
|
94
107
|
</li>
|
|
95
108
|
{index < steps.length - 1 ? (
|
|
96
|
-
<li aria-hidden className="mr-
|
|
109
|
+
<li aria-hidden className="mr-1.5 ml-0.5 flex shrink-0 items-stretch md:mr-6 md:ml-1">
|
|
97
110
|
<StepDivider />
|
|
98
111
|
</li>
|
|
99
112
|
) : null}
|
package/registry.json
CHANGED
|
@@ -128,14 +128,8 @@
|
|
|
128
128
|
"name": "stepper",
|
|
129
129
|
"title": "Stepper",
|
|
130
130
|
"description": "Progress rail for multi-step flows: a mobile progress bar plus a clickable desktop rail.",
|
|
131
|
-
"dependencies": ["next"],
|
|
132
131
|
"registryDependencies": ["utils", "brand-theme"],
|
|
133
132
|
"files": [
|
|
134
|
-
{
|
|
135
|
-
"source": "public/assets/images/breadcrumb_arrow.svg",
|
|
136
|
-
"path": "assets/breadcrumb_arrow.svg",
|
|
137
|
-
"target": "public/assets/images/breadcrumb_arrow.svg"
|
|
138
|
-
},
|
|
139
133
|
{
|
|
140
134
|
"source": "src/components/ui/stepper/index.tsx",
|
|
141
135
|
"path": "components/ui/stepper/index.tsx"
|
|
@@ -156,10 +150,6 @@
|
|
|
156
150
|
"source": "src/components/ui/stepper/step-label.tsx",
|
|
157
151
|
"path": "components/ui/stepper/step-label.tsx"
|
|
158
152
|
},
|
|
159
|
-
{
|
|
160
|
-
"source": "src/components/ui/stepper/stepper-progress.tsx",
|
|
161
|
-
"path": "components/ui/stepper/stepper-progress.tsx"
|
|
162
|
-
},
|
|
163
153
|
{
|
|
164
154
|
"source": "src/components/ui/stepper/stepper-rail.tsx",
|
|
165
155
|
"path": "components/ui/stepper/stepper-rail.tsx"
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { memo } from "react";
|
|
4
|
-
|
|
5
|
-
import { cn } from "@/lib/utils";
|
|
6
|
-
import type { StepperLayoutProps } from "@/components/ui/stepper/types";
|
|
7
|
-
|
|
8
|
-
/** Mobile layout: active step name, counter, and a segmented progress bar. */
|
|
9
|
-
const StepperProgress = memo(function StepperProgress({
|
|
10
|
-
steps,
|
|
11
|
-
currentStep,
|
|
12
|
-
}: Omit<StepperLayoutProps, "onStepChange">) {
|
|
13
|
-
const totalSteps = steps.length;
|
|
14
|
-
const activeIndex = Math.min(Math.max(currentStep, 0), totalSteps - 1);
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<div className="rounded-2xl border border-zinc-200 bg-white p-4 md:hidden">
|
|
18
|
-
<div className="flex items-baseline justify-between gap-3">
|
|
19
|
-
<span className="text-brand text-sm font-semibold">{steps[activeIndex]?.label}</span>
|
|
20
|
-
<span className="shrink-0 text-xs font-medium text-zinc-400 tabular-nums">
|
|
21
|
-
{activeIndex + 1} / {totalSteps}
|
|
22
|
-
</span>
|
|
23
|
-
</div>
|
|
24
|
-
<div className="mt-3 flex gap-1.5" role="presentation">
|
|
25
|
-
{steps.map((step, index) => (
|
|
26
|
-
<span
|
|
27
|
-
key={step.label}
|
|
28
|
-
title={step.label}
|
|
29
|
-
className={cn(
|
|
30
|
-
"h-1.5 flex-1 rounded-full transition-colors duration-300",
|
|
31
|
-
index <= currentStep ? "bg-brand" : "bg-zinc-200",
|
|
32
|
-
)}
|
|
33
|
-
/>
|
|
34
|
-
))}
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
export { StepperProgress };
|