aural-ui 2.1.8 → 2.1.10

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.
@@ -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,230 @@
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 = () => {
64
+ if (isClickable && onItemClick) {
65
+ onItemClick(title, url)
66
+ }
67
+ }
68
+
69
+ const renderSeparator = () => {
70
+ if (isLast) return null
71
+
72
+ if (customSeparator) {
73
+ return (
74
+ <span
75
+ className="text-fm-tertiary mx-1 md:mx-2"
76
+ data-testid="custom-separator"
77
+ >
78
+ {customSeparator}
79
+ </span>
80
+ )
81
+ }
82
+
83
+ switch (separator) {
84
+ case "slash":
85
+ return (
86
+ <span
87
+ className="text-fm-tertiary mx-1 md:mx-2"
88
+ data-testid="slash-separator"
89
+ >
90
+ /
91
+ </span>
92
+ )
93
+ case "arrow":
94
+ return (
95
+ <span
96
+ className="text-fm-tertiary mx-1 md:mx-2"
97
+ data-testid="arrow-separator"
98
+ >
99
+
100
+ </span>
101
+ )
102
+ case "chevron":
103
+ default:
104
+ return (
105
+ <ChevronRightIcon
106
+ className="text-fm-tertiary"
107
+ width={16}
108
+ height={16}
109
+ data-testid="chevron-right"
110
+ />
111
+ )
112
+ }
113
+ }
114
+
115
+ const typographyVariantMap = {
116
+ sm: "caption-medium",
117
+ lg: "body-medium",
118
+ md: "body-small",
119
+ } as const
120
+
121
+ const typographyVariant =
122
+ typographyVariantMap[size] || typographyVariantMap.md
123
+
124
+ return (
125
+ <div className="flex items-center gap-1 md:gap-3">
126
+ {isClickable && url ? (
127
+ <button
128
+ onClick={handleClick}
129
+ className={cn(
130
+ "focus-visible:ring-ring rounded-sm transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-offset-2",
131
+ className
132
+ )}
133
+ data-testid="breadcrumb-link"
134
+ >
135
+ <Typography
136
+ variant={typographyVariant}
137
+ className="text-fm-primary hover:text-fm-primary-600 max-w-[96px] cursor-pointer truncate"
138
+ >
139
+ {title}
140
+ </Typography>
141
+ </button>
142
+ ) : (
143
+ <Typography
144
+ variant={typographyVariant}
145
+ className={cn("text-fm-secondary max-w-[96px] truncate", className)}
146
+ data-testid="breadcrumb-text"
147
+ >
148
+ {title}
149
+ </Typography>
150
+ )}
151
+ {renderSeparator()}
152
+ </div>
153
+ )
154
+ }
155
+
156
+ const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>(
157
+ (
158
+ {
159
+ items,
160
+ onItemClick,
161
+ className = "",
162
+ size = "md",
163
+ separator = "chevron",
164
+ maxItems,
165
+ showHome = false,
166
+ homeTitle = "Home",
167
+ homeUrl = "/",
168
+ customSeparator,
169
+ ...props
170
+ },
171
+ ref
172
+ ) => {
173
+ // Add home item if showHome is true
174
+ const allItems = showHome
175
+ ? [{ title: homeTitle, url: homeUrl, isClickable: true }, ...items]
176
+ : items
177
+
178
+ // Limit items if maxItems is specified
179
+ const displayItems = (() => {
180
+ if (!maxItems || allItems.length <= maxItems) return allItems
181
+
182
+ if (maxItems >= 3) {
183
+ return [
184
+ allItems[0],
185
+ { title: "...", isClickable: false },
186
+ ...allItems.slice(-(maxItems - 2)),
187
+ ]
188
+ }
189
+
190
+ if (maxItems === 2) {
191
+ return [allItems[0], allItems[allItems.length - 1]]
192
+ }
193
+
194
+ return allItems.slice(0, maxItems)
195
+ })()
196
+
197
+ return (
198
+ <nav
199
+ ref={ref}
200
+ className={cn(breadcrumbVariants({ size, separator }), className)}
201
+ aria-label="Breadcrumb"
202
+ {...props}
203
+ >
204
+ <ol
205
+ className="flex items-center gap-1 md:gap-3"
206
+ data-testid="breadcrumb-list"
207
+ >
208
+ {displayItems.map((item, index) => (
209
+ <li key={`${item.title}-${index}`} className="flex items-center">
210
+ <BreadCrumbItem
211
+ {...item}
212
+ onItemClick={onItemClick}
213
+ isLast={index === displayItems.length - 1}
214
+ size={size || "md"}
215
+ separator={separator || "chevron"}
216
+ className={item.className}
217
+ customSeparator={customSeparator}
218
+ />
219
+ </li>
220
+ ))}
221
+ </ol>
222
+ </nav>
223
+ )
224
+ }
225
+ )
226
+
227
+ Breadcrumb.displayName = "Breadcrumb"
228
+
229
+ export { Breadcrumb, BreadCrumbItem }
230
+ 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
+ }
@@ -9,7 +9,7 @@ import { cn } from "@lib/utils"
9
9
  type HelperTextVariant = "default" | "error" | "warning" | "success"
10
10
 
11
11
  interface HelperTextProps {
12
- variant: HelperTextVariant
12
+ variant?: HelperTextVariant
13
13
  className?: string
14
14
  children: ReactNode
15
15
  disabled?: boolean
@@ -17,7 +17,7 @@ interface HelperTextProps {
17
17
  }
18
18
 
19
19
  const HelperText = forwardRef<HTMLSpanElement, HelperTextProps>(
20
- ({ variant, className = "", children, disabled, id }, ref) => {
20
+ ({ variant = "default", className = "", children, disabled, id }, ref) => {
21
21
  return (
22
22
  <SwitchCase value={variant}>
23
23
  <Case value="error">
@@ -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"