aural-ui 2.1.9 → 2.1.11
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/breadcrumb/Breadcrumb.stories.tsx +302 -0
- package/dist/components/breadcrumb/index.tsx +232 -0
- package/dist/components/breadcrumb/meta.ts +14 -0
- package/dist/components/index.ts +3 -0
- package/dist/components/otp-inputs/index.tsx +39 -4
- package/dist/components/textarea/index.tsx +1 -1
- package/dist/components/typography/meta.ts +1 -0
- package/dist/fonts/Nunito-VariableFont_wght.ttf +0 -0
- package/dist/icons/index.ts +2 -0
- package/dist/icons/phone-icon/PhoneIcon.stories.tsx +1034 -0
- package/dist/icons/phone-icon/index.tsx +26 -0
- package/dist/icons/phone-icon/meta.ts +8 -0
- package/dist/icons/shield-icon/ShieldIcon.stories.tsx +991 -0
- package/dist/icons/shield-icon/index.tsx +21 -0
- package/dist/icons/shield-icon/meta.ts +8 -0
- package/dist/index.js +1 -1
- package/dist/styles/aural-theme.css +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { ArrowRightIcon } from "@icons/arrow-right-icon"
|
|
3
|
+
import { ChevronDoubleRightIcon } from "@icons/chevron-double-right-icon"
|
|
4
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
5
|
+
|
|
6
|
+
import { Breadcrumb } from "."
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Breadcrumb> = {
|
|
9
|
+
title: "Components/UI/Breadcrumb",
|
|
10
|
+
component: Breadcrumb,
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: "centered",
|
|
13
|
+
backgrounds: {
|
|
14
|
+
default: "dark",
|
|
15
|
+
values: [
|
|
16
|
+
{ name: "dark", value: "#0a0a0a" },
|
|
17
|
+
{ name: "light", value: "#ffffff" },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
docs: {
|
|
21
|
+
description: {
|
|
22
|
+
component: `
|
|
23
|
+
# Breadcrumb Component
|
|
24
|
+
|
|
25
|
+
A navigation component that shows the current page's location within a site hierarchy. Users can navigate back to previous levels by clicking on breadcrumb items.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **Multiple Separators**: Support for chevron, slash, arrow, and custom separators
|
|
30
|
+
- **Custom Separator**: Use any React component or icon as a separator
|
|
31
|
+
- **Clickable Navigation**: Items can be clickable links or static text
|
|
32
|
+
- **Responsive Design**: Adapts spacing for mobile and desktop
|
|
33
|
+
- **Accessibility**: Proper ARIA labels and semantic HTML structure
|
|
34
|
+
- **Customizable**: Size variants and custom styling options
|
|
35
|
+
- **Truncation**: Optional max items with ellipsis for long breadcrumbs
|
|
36
|
+
- **Home Integration**: Optional home item at the beginning
|
|
37
|
+
|
|
38
|
+
## Usage Examples
|
|
39
|
+
|
|
40
|
+
### Basic Breadcrumb
|
|
41
|
+
\`\`\`tsx
|
|
42
|
+
<Breadcrumb
|
|
43
|
+
items={[
|
|
44
|
+
{ title: "Home", url: "/" },
|
|
45
|
+
{ title: "Products", url: "/products" },
|
|
46
|
+
{ title: "Electronics" }
|
|
47
|
+
]}
|
|
48
|
+
onItemClick={(title, url) => console.log(title, url)}
|
|
49
|
+
/>
|
|
50
|
+
\`\`\`
|
|
51
|
+
|
|
52
|
+
### With Different Separators
|
|
53
|
+
\`\`\`tsx
|
|
54
|
+
<Breadcrumb
|
|
55
|
+
items={items}
|
|
56
|
+
separator="slash"
|
|
57
|
+
onItemClick={handleClick}
|
|
58
|
+
/>
|
|
59
|
+
\`\`\`
|
|
60
|
+
|
|
61
|
+
### With Size Variants
|
|
62
|
+
\`\`\`tsx
|
|
63
|
+
<Breadcrumb
|
|
64
|
+
items={items}
|
|
65
|
+
size="lg"
|
|
66
|
+
onItemClick={handleClick}
|
|
67
|
+
/>
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
### With Custom Separator
|
|
71
|
+
\`\`\`tsx
|
|
72
|
+
<Breadcrumb
|
|
73
|
+
items={items}
|
|
74
|
+
customSeparator={<ArrowRightIcon width={16} height={16} />}
|
|
75
|
+
onItemClick={handleClick}
|
|
76
|
+
/>
|
|
77
|
+
\`\`\`
|
|
78
|
+
`,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
tags: ["autodocs"],
|
|
83
|
+
argTypes: {
|
|
84
|
+
size: {
|
|
85
|
+
control: { type: "select" },
|
|
86
|
+
options: ["sm", "md", "lg"],
|
|
87
|
+
},
|
|
88
|
+
separator: {
|
|
89
|
+
control: { type: "select" },
|
|
90
|
+
options: ["chevron", "slash", "arrow"],
|
|
91
|
+
},
|
|
92
|
+
showHome: {
|
|
93
|
+
control: { type: "boolean" },
|
|
94
|
+
},
|
|
95
|
+
maxItems: {
|
|
96
|
+
control: { type: "number" },
|
|
97
|
+
},
|
|
98
|
+
onItemClick: {
|
|
99
|
+
action: "item clicked",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default meta
|
|
105
|
+
type Story = StoryObj<typeof Breadcrumb>
|
|
106
|
+
|
|
107
|
+
const sampleItems = [
|
|
108
|
+
{ title: "Home", url: "/", isClickable: true },
|
|
109
|
+
{ title: "Products", url: "/products", isClickable: true },
|
|
110
|
+
{ title: "Electronics", url: "/products/electronics", isClickable: true },
|
|
111
|
+
{
|
|
112
|
+
title: "Smartphones",
|
|
113
|
+
url: "/products/electronics/smartphones",
|
|
114
|
+
isClickable: true,
|
|
115
|
+
},
|
|
116
|
+
{ title: "iPhone 15 Pro" },
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
const longItems = [
|
|
120
|
+
{ title: "Home", url: "/", isClickable: true },
|
|
121
|
+
{ title: "Products", url: "/products", isClickable: true },
|
|
122
|
+
{ title: "Electronics", url: "/products/electronics", isClickable: true },
|
|
123
|
+
{
|
|
124
|
+
title: "Smartphones",
|
|
125
|
+
url: "/products/electronics/smartphones",
|
|
126
|
+
isClickable: true,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
title: "Apple",
|
|
130
|
+
url: "/products/electronics/smartphones/apple",
|
|
131
|
+
isClickable: true,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
title: "iPhone",
|
|
135
|
+
url: "/products/electronics/smartphones/apple/iphone",
|
|
136
|
+
isClickable: true,
|
|
137
|
+
},
|
|
138
|
+
{ title: "iPhone 15 Pro" },
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
export const Default: Story = {
|
|
142
|
+
args: {
|
|
143
|
+
items: sampleItems,
|
|
144
|
+
size: "md",
|
|
145
|
+
separator: "chevron",
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const WithSlashSeparator: Story = {
|
|
150
|
+
args: {
|
|
151
|
+
items: sampleItems,
|
|
152
|
+
separator: "slash",
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const WithArrowSeparator: Story = {
|
|
157
|
+
args: {
|
|
158
|
+
items: sampleItems,
|
|
159
|
+
separator: "arrow",
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const WithCustomSeparator: Story = {
|
|
164
|
+
args: {
|
|
165
|
+
items: sampleItems,
|
|
166
|
+
customSeparator: <ChevronDoubleRightIcon width={16} height={16} />,
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const SmallSize: Story = {
|
|
171
|
+
args: {
|
|
172
|
+
items: sampleItems,
|
|
173
|
+
size: "sm",
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const LargeSize: Story = {
|
|
178
|
+
args: {
|
|
179
|
+
items: sampleItems,
|
|
180
|
+
size: "lg",
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const WithHome: Story = {
|
|
185
|
+
args: {
|
|
186
|
+
items: [
|
|
187
|
+
{ title: "Products", url: "/products", isClickable: true },
|
|
188
|
+
{ title: "Electronics", url: "/products/electronics", isClickable: true },
|
|
189
|
+
{ title: "Smartphones" },
|
|
190
|
+
],
|
|
191
|
+
showHome: true,
|
|
192
|
+
homeTitle: "Home",
|
|
193
|
+
homeUrl: "/",
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export const WithMaxItems: Story = {
|
|
198
|
+
args: {
|
|
199
|
+
items: longItems,
|
|
200
|
+
maxItems: 4,
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const NonClickable: Story = {
|
|
205
|
+
args: {
|
|
206
|
+
items: [
|
|
207
|
+
{ title: "Home", isClickable: false },
|
|
208
|
+
{ title: "Products", isClickable: false },
|
|
209
|
+
{ title: "Electronics", isClickable: false },
|
|
210
|
+
{ title: "Smartphones" },
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export const MixedClickable: Story = {
|
|
216
|
+
args: {
|
|
217
|
+
items: [
|
|
218
|
+
{ title: "Home", url: "/", isClickable: true },
|
|
219
|
+
{ title: "Products", isClickable: false },
|
|
220
|
+
{ title: "Electronics", url: "/products/electronics", isClickable: true },
|
|
221
|
+
{ title: "Smartphones" },
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export const AllVariants: Story = {
|
|
227
|
+
render: () => (
|
|
228
|
+
<div className="flex flex-col gap-8 p-8">
|
|
229
|
+
<div className="space-y-4">
|
|
230
|
+
<h3 className="text-fm-lg font-fm-brand text-neutral-400">
|
|
231
|
+
Size Variants
|
|
232
|
+
</h3>
|
|
233
|
+
<div className="space-y-2">
|
|
234
|
+
<Breadcrumb items={sampleItems} size="sm" />
|
|
235
|
+
<Breadcrumb items={sampleItems} size="md" />
|
|
236
|
+
<Breadcrumb items={sampleItems} size="lg" />
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div className="space-y-4">
|
|
241
|
+
<h3 className="text-fm-lg font-fm-brand text-neutral-400">
|
|
242
|
+
Separator Variants
|
|
243
|
+
</h3>
|
|
244
|
+
<div className="space-y-2">
|
|
245
|
+
<Breadcrumb items={sampleItems} separator="chevron" />
|
|
246
|
+
<Breadcrumb items={sampleItems} separator="slash" />
|
|
247
|
+
<Breadcrumb items={sampleItems} separator="arrow" />
|
|
248
|
+
<Breadcrumb
|
|
249
|
+
items={sampleItems}
|
|
250
|
+
customSeparator={<ArrowRightIcon width={16} height={16} />}
|
|
251
|
+
/>
|
|
252
|
+
<Breadcrumb
|
|
253
|
+
items={sampleItems}
|
|
254
|
+
customSeparator={<ChevronDoubleRightIcon width={16} height={16} />}
|
|
255
|
+
/>
|
|
256
|
+
<Breadcrumb items={sampleItems} customSeparator="•" />
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div className="space-y-4">
|
|
261
|
+
<h3 className="text-fm-lg font-fm-brand text-neutral-400">With Home</h3>
|
|
262
|
+
<Breadcrumb
|
|
263
|
+
items={[
|
|
264
|
+
{ title: "Products", url: "/products", isClickable: true },
|
|
265
|
+
{ title: "Electronics" },
|
|
266
|
+
]}
|
|
267
|
+
showHome={true}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div className="space-y-4">
|
|
272
|
+
<h3 className="text-fm-lg font-fm-brand text-neutral-400">
|
|
273
|
+
With Max Items (4)
|
|
274
|
+
</h3>
|
|
275
|
+
<Breadcrumb items={longItems} maxItems={4} />
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
),
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export const Interactive: Story = {
|
|
282
|
+
render: () => {
|
|
283
|
+
const [currentPath, setCurrentPath] = React.useState("iPhone 15 Pro")
|
|
284
|
+
|
|
285
|
+
const handleItemClick = (title: string, url?: string) => {
|
|
286
|
+
console.log(`Clicked: ${title}`, url)
|
|
287
|
+
setCurrentPath(title)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<div className="space-y-4 p-8">
|
|
292
|
+
<h3 className="text-fm-lg font-fm-brand text-neutral-400">
|
|
293
|
+
Current Path: {currentPath}
|
|
294
|
+
</h3>
|
|
295
|
+
<Breadcrumb items={sampleItems} onItemClick={handleItemClick} />
|
|
296
|
+
<div className="text-fm-secondary text-fm-sm">
|
|
297
|
+
Click on any breadcrumb item to see the interaction
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
},
|
|
302
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import React, { forwardRef } from "react"
|
|
2
|
+
import { ChevronRightIcon } from "@icons/chevron-right-icon"
|
|
3
|
+
import { cn } from "@lib/utils"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
|
|
6
|
+
import { Typography } from "../typography"
|
|
7
|
+
|
|
8
|
+
export const breadcrumbVariants = cva("flex items-center gap-1 md:gap-3", {
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: "text-fm-sm",
|
|
12
|
+
md: "text-fm-md",
|
|
13
|
+
lg: "text-fm-lg",
|
|
14
|
+
},
|
|
15
|
+
separator: {
|
|
16
|
+
chevron: "",
|
|
17
|
+
slash: "",
|
|
18
|
+
arrow: "",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
size: "md",
|
|
23
|
+
separator: "chevron",
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export interface BreadCrumbsItemProps {
|
|
28
|
+
title: string
|
|
29
|
+
url?: string
|
|
30
|
+
isClickable?: boolean
|
|
31
|
+
className?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BreadcrumbProps
|
|
35
|
+
extends VariantProps<typeof breadcrumbVariants> {
|
|
36
|
+
items: BreadCrumbsItemProps[]
|
|
37
|
+
onItemClick?: (title: string, url?: string) => void
|
|
38
|
+
className?: string
|
|
39
|
+
maxItems?: number
|
|
40
|
+
showHome?: boolean
|
|
41
|
+
homeTitle?: string
|
|
42
|
+
homeUrl?: string
|
|
43
|
+
customSeparator?: React.ReactNode
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const BreadCrumbItem = ({
|
|
47
|
+
title,
|
|
48
|
+
url,
|
|
49
|
+
isClickable = true,
|
|
50
|
+
className = "",
|
|
51
|
+
onItemClick,
|
|
52
|
+
isLast = false,
|
|
53
|
+
size = "md",
|
|
54
|
+
separator = "chevron",
|
|
55
|
+
customSeparator,
|
|
56
|
+
}: BreadCrumbsItemProps & {
|
|
57
|
+
onItemClick?: (title: string, url?: string) => void
|
|
58
|
+
isLast: boolean
|
|
59
|
+
size?: "sm" | "md" | "lg"
|
|
60
|
+
separator?: "chevron" | "slash" | "arrow"
|
|
61
|
+
customSeparator?: React.ReactNode
|
|
62
|
+
}) => {
|
|
63
|
+
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
64
|
+
e.preventDefault()
|
|
65
|
+
if (isClickable && onItemClick) {
|
|
66
|
+
onItemClick(title, url)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const renderSeparator = () => {
|
|
71
|
+
if (isLast) return null
|
|
72
|
+
|
|
73
|
+
if (customSeparator) {
|
|
74
|
+
return (
|
|
75
|
+
<span
|
|
76
|
+
className="text-fm-tertiary mx-1 md:mx-2"
|
|
77
|
+
data-testid="custom-separator"
|
|
78
|
+
>
|
|
79
|
+
{customSeparator}
|
|
80
|
+
</span>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
switch (separator) {
|
|
85
|
+
case "slash":
|
|
86
|
+
return (
|
|
87
|
+
<span
|
|
88
|
+
className="text-fm-tertiary mx-1 md:mx-2"
|
|
89
|
+
data-testid="slash-separator"
|
|
90
|
+
>
|
|
91
|
+
/
|
|
92
|
+
</span>
|
|
93
|
+
)
|
|
94
|
+
case "arrow":
|
|
95
|
+
return (
|
|
96
|
+
<span
|
|
97
|
+
className="text-fm-tertiary mx-1 md:mx-2"
|
|
98
|
+
data-testid="arrow-separator"
|
|
99
|
+
>
|
|
100
|
+
→
|
|
101
|
+
</span>
|
|
102
|
+
)
|
|
103
|
+
case "chevron":
|
|
104
|
+
default:
|
|
105
|
+
return (
|
|
106
|
+
<ChevronRightIcon
|
|
107
|
+
className="text-fm-tertiary"
|
|
108
|
+
width={16}
|
|
109
|
+
height={16}
|
|
110
|
+
data-testid="chevron-right"
|
|
111
|
+
/>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const typographyVariantMap = {
|
|
117
|
+
sm: "caption-medium",
|
|
118
|
+
lg: "body-medium",
|
|
119
|
+
md: "body-small",
|
|
120
|
+
} as const
|
|
121
|
+
|
|
122
|
+
const typographyVariant =
|
|
123
|
+
typographyVariantMap[size] || typographyVariantMap.md
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div className="flex items-center gap-1 md:gap-3">
|
|
127
|
+
{isClickable && url ? (
|
|
128
|
+
<a
|
|
129
|
+
href={url}
|
|
130
|
+
onClick={handleClick}
|
|
131
|
+
className={cn(
|
|
132
|
+
"focus-visible:ring-ring rounded-sm transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-offset-2",
|
|
133
|
+
className
|
|
134
|
+
)}
|
|
135
|
+
data-testid="breadcrumb-link"
|
|
136
|
+
>
|
|
137
|
+
<Typography
|
|
138
|
+
variant={typographyVariant}
|
|
139
|
+
className="text-fm-primary hover:text-fm-primary-600 max-w-[96px] cursor-pointer truncate"
|
|
140
|
+
>
|
|
141
|
+
{title}
|
|
142
|
+
</Typography>
|
|
143
|
+
</a>
|
|
144
|
+
) : (
|
|
145
|
+
<Typography
|
|
146
|
+
variant={typographyVariant}
|
|
147
|
+
className={cn("text-fm-secondary max-w-[96px] truncate", className)}
|
|
148
|
+
data-testid="breadcrumb-text"
|
|
149
|
+
>
|
|
150
|
+
{title}
|
|
151
|
+
</Typography>
|
|
152
|
+
)}
|
|
153
|
+
{renderSeparator()}
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>(
|
|
159
|
+
(
|
|
160
|
+
{
|
|
161
|
+
items,
|
|
162
|
+
onItemClick,
|
|
163
|
+
className = "",
|
|
164
|
+
size = "md",
|
|
165
|
+
separator = "chevron",
|
|
166
|
+
maxItems,
|
|
167
|
+
showHome = false,
|
|
168
|
+
homeTitle = "Home",
|
|
169
|
+
homeUrl = "/",
|
|
170
|
+
customSeparator,
|
|
171
|
+
...props
|
|
172
|
+
},
|
|
173
|
+
ref
|
|
174
|
+
) => {
|
|
175
|
+
// Add home item if showHome is true
|
|
176
|
+
const allItems = showHome
|
|
177
|
+
? [{ title: homeTitle, url: homeUrl, isClickable: true }, ...items]
|
|
178
|
+
: items
|
|
179
|
+
|
|
180
|
+
// Limit items if maxItems is specified
|
|
181
|
+
const displayItems = (() => {
|
|
182
|
+
if (!maxItems || allItems.length <= maxItems) return allItems
|
|
183
|
+
|
|
184
|
+
if (maxItems >= 3) {
|
|
185
|
+
return [
|
|
186
|
+
allItems[0],
|
|
187
|
+
{ title: "...", isClickable: false },
|
|
188
|
+
...allItems.slice(-(maxItems - 2)),
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (maxItems === 2) {
|
|
193
|
+
return [allItems[0], allItems[allItems.length - 1]]
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return allItems.slice(0, maxItems)
|
|
197
|
+
})()
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<nav
|
|
201
|
+
ref={ref}
|
|
202
|
+
className={cn(breadcrumbVariants({ size, separator }), className)}
|
|
203
|
+
aria-label="Breadcrumb"
|
|
204
|
+
{...props}
|
|
205
|
+
>
|
|
206
|
+
<ol
|
|
207
|
+
className="flex items-center gap-1 md:gap-3"
|
|
208
|
+
data-testid="breadcrumb-list"
|
|
209
|
+
>
|
|
210
|
+
{displayItems.map((item, index) => (
|
|
211
|
+
<li key={`${item.title}-${index}`} className="flex items-center">
|
|
212
|
+
<BreadCrumbItem
|
|
213
|
+
{...item}
|
|
214
|
+
onItemClick={onItemClick}
|
|
215
|
+
isLast={index === displayItems.length - 1}
|
|
216
|
+
size={size || "md"}
|
|
217
|
+
separator={separator || "chevron"}
|
|
218
|
+
className={item.className}
|
|
219
|
+
customSeparator={customSeparator}
|
|
220
|
+
/>
|
|
221
|
+
</li>
|
|
222
|
+
))}
|
|
223
|
+
</ol>
|
|
224
|
+
</nav>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
Breadcrumb.displayName = "Breadcrumb"
|
|
230
|
+
|
|
231
|
+
export { Breadcrumb, BreadCrumbItem }
|
|
232
|
+
export default Breadcrumb
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const meta = {
|
|
2
|
+
dependencies: {},
|
|
3
|
+
devDependencies: {},
|
|
4
|
+
internalDependencies: ["typography", "chevron-right-icon"],
|
|
5
|
+
tokens: [
|
|
6
|
+
"--color-fm-primary",
|
|
7
|
+
"--color-fm-primary-600",
|
|
8
|
+
"--color-fm-secondary",
|
|
9
|
+
"--color-fm-tertiary",
|
|
10
|
+
"--text-fm-sm",
|
|
11
|
+
"--text-fm-md",
|
|
12
|
+
"--text-fm-lg",
|
|
13
|
+
],
|
|
14
|
+
}
|
package/dist/components/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./aspect-ratio"
|
|
|
3
3
|
export * from "./avatar"
|
|
4
4
|
export * from "./badge"
|
|
5
5
|
export * from "./banner"
|
|
6
|
+
export * from "./breadcrumb"
|
|
6
7
|
export * from "./button"
|
|
7
8
|
export * from "./card"
|
|
8
9
|
export * from "./char-count"
|
|
@@ -21,7 +22,9 @@ export * from "./if-else"
|
|
|
21
22
|
export * from "./label"
|
|
22
23
|
export * from "./list"
|
|
23
24
|
export * from "./marquee"
|
|
25
|
+
export * from "./otp-inputs"
|
|
24
26
|
export * from "./overlay"
|
|
27
|
+
export * from "./otp-inputs"
|
|
25
28
|
export * from "./pagination"
|
|
26
29
|
export * from "./popover"
|
|
27
30
|
export * from "./radio"
|
|
@@ -31,6 +31,8 @@ interface SingleOtpInputType {
|
|
|
31
31
|
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
|
32
32
|
onFocus: () => void
|
|
33
33
|
onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void
|
|
34
|
+
onPaste?: (event: React.ClipboardEvent<HTMLInputElement>) => void
|
|
35
|
+
|
|
34
36
|
style?: React.CSSProperties
|
|
35
37
|
value: string | undefined
|
|
36
38
|
type?: "text" | "number"
|
|
@@ -100,10 +102,6 @@ export default function OtpInputs(props: OTPInputsType) {
|
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
const onBlur = () => {
|
|
104
|
-
setActiveInput(-1)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
105
|
const handleOnKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
108
106
|
switch (e.key) {
|
|
109
107
|
case "Backspace":
|
|
@@ -138,6 +136,42 @@ export default function OtpInputs(props: OTPInputsType) {
|
|
|
138
136
|
}
|
|
139
137
|
}
|
|
140
138
|
|
|
139
|
+
const handleOnPaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
|
|
140
|
+
e.preventDefault()
|
|
141
|
+
const pastedData = e.clipboardData.getData("text/plain")
|
|
142
|
+
|
|
143
|
+
const validChars = pastedData
|
|
144
|
+
.split("")
|
|
145
|
+
.map((char) => getRightValue(char))
|
|
146
|
+
.filter((char) => char !== "")
|
|
147
|
+
.slice(0, length)
|
|
148
|
+
|
|
149
|
+
if (validChars.length === 0) return
|
|
150
|
+
|
|
151
|
+
const updatedOTPValues = [...otpValues]
|
|
152
|
+
let currentIndex = activeInput
|
|
153
|
+
for (let i = 0; i < validChars.length && currentIndex < length; i++) {
|
|
154
|
+
updatedOTPValues[currentIndex] = validChars[i]
|
|
155
|
+
currentIndex++
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setOTPValues(updatedOTPValues)
|
|
159
|
+
handleOtpChange(updatedOTPValues)
|
|
160
|
+
|
|
161
|
+
const nextEmptyIndex = updatedOTPValues.findIndex(
|
|
162
|
+
(val, idx) => idx >= activeInput && val === ""
|
|
163
|
+
)
|
|
164
|
+
const focusIndex =
|
|
165
|
+
nextEmptyIndex !== -1
|
|
166
|
+
? nextEmptyIndex
|
|
167
|
+
: Math.min(length - 1, activeInput + validChars.length)
|
|
168
|
+
focusInput(focusIndex)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const onBlur = () => {
|
|
172
|
+
setActiveInput(-1)
|
|
173
|
+
}
|
|
174
|
+
|
|
141
175
|
const messagesMap = {
|
|
142
176
|
true: messages?.success ?? "✓ Valid input",
|
|
143
177
|
false: messages?.error ?? "✗ Invalid input. Try again",
|
|
@@ -168,6 +202,7 @@ export default function OtpInputs(props: OTPInputsType) {
|
|
|
168
202
|
value={otpValues && otpValues[index]}
|
|
169
203
|
autoComplete="off"
|
|
170
204
|
onFocus={handleOnFocus(index)}
|
|
205
|
+
onPaste={handleOnPaste}
|
|
171
206
|
onChange={handleOnChange}
|
|
172
207
|
onKeyDown={handleOnKeyDown}
|
|
173
208
|
onBlur={onBlur}
|
|
Binary file
|
package/dist/icons/index.ts
CHANGED
|
@@ -71,6 +71,7 @@ export * from "./paper-plane-icon"
|
|
|
71
71
|
export * from "./pause-icon"
|
|
72
72
|
export * from "./pencil-icon"
|
|
73
73
|
export * from "./plus-icon"
|
|
74
|
+
export * from "./phone-icon"
|
|
74
75
|
export * from "./search-icon"
|
|
75
76
|
export * from "./setting-icon"
|
|
76
77
|
export * from "./share-icon"
|
|
@@ -84,6 +85,7 @@ export * from "./spinner-solid-neutral-icon"
|
|
|
84
85
|
export * from "./star-icon"
|
|
85
86
|
export * from "./store-coin-icon"
|
|
86
87
|
export * from "./suggestion-icon"
|
|
88
|
+
export * from "./shield-icon"
|
|
87
89
|
export * from "./sun-icon"
|
|
88
90
|
export * from "./text-color-icon"
|
|
89
91
|
export * from "./text-indicator-icon"
|