aural-ui 4.2.3 → 4.2.5

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.
@@ -23,9 +23,9 @@ export * from "./if-else"
23
23
  export * from "./label"
24
24
  export * from "./list"
25
25
  export * from "./marquee"
26
+ export * from "./offer-label"
26
27
  export * from "./otp-inputs"
27
28
  export * from "./overlay"
28
- export * from "./otp-inputs"
29
29
  export * from "./pagination"
30
30
  export * from "./popover"
31
31
  export * from "./radio"
@@ -0,0 +1,267 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react-vite"
3
+
4
+ import { AuralComponentDocsPage } from "src/ui/story-spec/components/component-story-docs-page"
5
+
6
+ import { OfferLabel } from "."
7
+
8
+ const meta: Meta<typeof OfferLabel> = {
9
+ title: "Components/UI/OfferLabel",
10
+ component: OfferLabel,
11
+ parameters: {
12
+ layout: "centered",
13
+ docs: {
14
+ description: {
15
+ component:
16
+ "An inline label that renders text with a decorative gradient underline stroke beneath it — ideal for surfacing promotional copy, highlighted offers, or key phrases.",
17
+ },
18
+ page: () => (
19
+ <AuralComponentDocsPage
20
+ features={[
21
+ {
22
+ title: "Gradient Underline",
23
+ description: "Animated SVG accent",
24
+ },
25
+ {
26
+ title: "Composable Text",
27
+ description: "Accepts any className",
28
+ },
29
+ {
30
+ title: "SSR Safe",
31
+ description: "Stable gradient ID prop",
32
+ },
33
+ ]}
34
+ />
35
+ ),
36
+ },
37
+ },
38
+ tags: ["autodocs"],
39
+ argTypes: {
40
+ text: {
41
+ control: "text",
42
+ description: "The label text to display",
43
+ },
44
+ className: {
45
+ control: "text",
46
+ description: "CSS classes applied to the inner text span",
47
+ },
48
+ iconId: {
49
+ control: "text",
50
+ description: "Optional stable ID for the underline gradient",
51
+ },
52
+ },
53
+ }
54
+
55
+ export default meta
56
+ type Story = StoryObj<typeof OfferLabel>
57
+
58
+ // ─── 1. Playground ────────────────────────────────────────────────────────────
59
+
60
+ export const Playground: Story = {
61
+ args: {
62
+ text: "Limited Offer",
63
+ className:
64
+ "text-fm-primary font-fm-brand text-fm-2xl leading-fm-2xl font-bold",
65
+ },
66
+ render: (args) => (
67
+ <div className="w-full max-w-sm space-y-4">
68
+ <div className="flex justify-center py-4">
69
+ <OfferLabel {...args} />
70
+ </div>
71
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border px-4 py-3">
72
+ <code className="text-fm-secondary text-fm-md leading-fm-md font-(--font-fm-mono)">{`<OfferLabel text="${args.text}" className="${args.className ?? ""}" />`}</code>
73
+ </div>
74
+ </div>
75
+ ),
76
+ parameters: {
77
+ docs: {
78
+ description: {
79
+ story:
80
+ "Adjust the text and className using the controls panel in the sidebar.",
81
+ },
82
+ },
83
+ },
84
+ }
85
+
86
+ // ─── 2. All Variants ──────────────────────────────────────────────────────────
87
+
88
+ export const AllVariants: Story = {
89
+ render: () => (
90
+ <div className="space-y-8">
91
+ <div className="space-y-4">
92
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
93
+ Typography Scale
94
+ </h4>
95
+ <div className="flex flex-wrap items-end gap-6">
96
+ <div className="space-y-2 text-center">
97
+ <OfferLabel
98
+ text="Headline"
99
+ className="text-fm-primary font-fm-brand text-fm-4xl leading-fm-4xl font-bold"
100
+ iconId="variant-4xl"
101
+ />
102
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
103
+ 4xl / bold
104
+ </p>
105
+ </div>
106
+ <div className="space-y-2 text-center">
107
+ <OfferLabel
108
+ text="Subheading"
109
+ className="text-fm-primary font-fm-brand text-fm-2xl leading-fm-2xl font-semibold"
110
+ iconId="variant-2xl"
111
+ />
112
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
113
+ 2xl / semibold
114
+ </p>
115
+ </div>
116
+ <div className="space-y-2 text-center">
117
+ <OfferLabel
118
+ text="Body label"
119
+ className="text-fm-primary font-fm-text text-fm-lg leading-fm-lg font-medium"
120
+ iconId="variant-lg"
121
+ />
122
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
123
+ lg / medium
124
+ </p>
125
+ </div>
126
+ <div className="space-y-2 text-center">
127
+ <OfferLabel
128
+ text="Small"
129
+ className="text-fm-primary font-fm-text text-fm-sm leading-fm-sm"
130
+ iconId="variant-sm"
131
+ />
132
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
133
+ sm / regular
134
+ </p>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <div className="space-y-4">
140
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
141
+ Text Color
142
+ </h4>
143
+ <div className="flex flex-wrap items-end gap-6">
144
+ <div className="space-y-2 text-center">
145
+ <OfferLabel
146
+ text="Primary"
147
+ className="text-fm-primary font-fm-brand text-fm-2xl leading-fm-2xl font-bold"
148
+ iconId="color-primary"
149
+ />
150
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
151
+ text-fm-primary
152
+ </p>
153
+ </div>
154
+ <div className="space-y-2 text-center">
155
+ <OfferLabel
156
+ text="Secondary"
157
+ className="text-fm-secondary font-fm-brand text-fm-2xl leading-fm-2xl font-bold"
158
+ iconId="color-secondary"
159
+ />
160
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
161
+ text-fm-secondary
162
+ </p>
163
+ </div>
164
+ <div className="space-y-2 text-center">
165
+ <OfferLabel
166
+ text="Tertiary"
167
+ className="text-fm-tertiary font-fm-brand text-fm-2xl leading-fm-2xl font-bold"
168
+ iconId="color-tertiary"
169
+ />
170
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
171
+ text-fm-tertiary
172
+ </p>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ ),
178
+ parameters: {
179
+ docs: {
180
+ description: {
181
+ story:
182
+ "OfferLabel across different type scales and text color tokens — the gradient underline adapts naturally to each.",
183
+ },
184
+ },
185
+ },
186
+ }
187
+
188
+ // ─── 3. Use Cases ─────────────────────────────────────────────────────────────
189
+
190
+ export const UseCases: Story = {
191
+ render: () => (
192
+ <div className="mx-auto max-w-3xl space-y-8 p-8">
193
+ <div className="space-y-4">
194
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
195
+ Promotional Hero Banner
196
+ </h4>
197
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary mx-auto max-w-md space-y-3 rounded-xl border p-6 text-center">
198
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm tracking-widest uppercase">
199
+ Pocket FM Premium
200
+ </p>
201
+ <div className="flex justify-center">
202
+ <OfferLabel
203
+ text="3 Months Free"
204
+ className="text-fm-primary font-fm-brand text-fm-4xl leading-fm-4xl font-bold"
205
+ iconId="use-case-hero"
206
+ />
207
+ </div>
208
+ <p className="text-fm-secondary font-fm-text text-fm-md leading-fm-md">
209
+ Unlock unlimited access to all audio stories.
210
+ </p>
211
+ </div>
212
+ </div>
213
+
214
+ <div className="space-y-4">
215
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
216
+ Inline Promotional Copy
217
+ </h4>
218
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary mx-auto max-w-md rounded-xl border p-5">
219
+ <p className="text-fm-primary font-fm-text text-fm-lg leading-fm-xl">
220
+ Subscribe today and get{" "}
221
+ <OfferLabel
222
+ text="50% off"
223
+ className="text-fm-primary font-fm-brand text-fm-lg leading-fm-xl font-semibold"
224
+ iconId="use-case-inline"
225
+ />{" "}
226
+ your first month — no strings attached.
227
+ </p>
228
+ </div>
229
+ </div>
230
+
231
+ <div className="space-y-4">
232
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
233
+ Pricing Card Highlight
234
+ </h4>
235
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary mx-auto max-w-sm space-y-4 rounded-xl border p-6">
236
+ <div className="space-y-1">
237
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm tracking-widest uppercase">
238
+ Annual Plan
239
+ </p>
240
+ <div className="flex items-baseline gap-1">
241
+ <OfferLabel
242
+ text="₹999"
243
+ className="text-fm-primary font-fm-brand text-fm-5xl leading-fm-5xl font-bold"
244
+ iconId="use-case-price"
245
+ />
246
+ <span className="text-fm-secondary font-fm-text text-fm-md leading-fm-md">
247
+ / year
248
+ </span>
249
+ </div>
250
+ </div>
251
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
252
+ Save 40% compared to the monthly plan.
253
+ </p>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ ),
258
+ parameters: {
259
+ layout: "fullscreen",
260
+ docs: {
261
+ description: {
262
+ story:
263
+ "Real-world usage: promotional hero banners, inline highlighted copy, and pricing card accents.",
264
+ },
265
+ },
266
+ },
267
+ }
@@ -0,0 +1,32 @@
1
+ import React from "react"
2
+ import { UnderlineIcon } from "@icons/underline-icon"
3
+ import { cn } from "@lib/utils"
4
+
5
+ export interface IOfferLabelProps {
6
+ className?: string
7
+ iconClass?: string
8
+ /**
9
+ * Optional stable ID for the underline gradient. Can help if SSR hydration mismatches occur.
10
+ */
11
+ iconId?: string
12
+ text: string
13
+ }
14
+
15
+ export function OfferLabel({
16
+ text,
17
+ className,
18
+ iconId,
19
+ iconClass,
20
+ }: IOfferLabelProps) {
21
+ return (
22
+ <span className="relative inline-block pb-1">
23
+ <span className={className}>{text}</span>
24
+ <UnderlineIcon
25
+ id={iconId}
26
+ aria-hidden="true"
27
+ withAccessibility={false}
28
+ className={cn("absolute left-0 w-8", iconClass)}
29
+ />
30
+ </span>
31
+ )
32
+ }
@@ -0,0 +1,6 @@
1
+ export const meta = {
2
+ dependencies: {},
3
+ devDependencies: {},
4
+ internalDependencies: ["underline-icon"],
5
+ tokens: [],
6
+ }
@@ -0,0 +1,167 @@
1
+ import React from "react"
2
+ import type { Meta, StoryObj } from "@storybook/react-vite"
3
+
4
+ import { ArrowRightUpIcon } from "src/ui/icons/arrow-right-up-icon"
5
+ import { CopyIcon } from "src/ui/icons/copy-icon"
6
+ import { GlobeIcon } from "src/ui/icons/globe-icon"
7
+ import { ShareIcon } from "src/ui/icons/share-icon"
8
+ import {
9
+ IconColorVariations,
10
+ IconDefaultCanvas,
11
+ IconPlaygroundCanvas,
12
+ IconSizeVariations,
13
+ IconUsageCanvas,
14
+ IconUsageSection,
15
+ } from "src/ui/story-spec/icons/icon-story-canvas"
16
+ import { AuralIconDocsPage } from "src/ui/story-spec/icons/icon-story-docs-page"
17
+
18
+ import { ChainLinkIcon } from "."
19
+
20
+ const meta: Meta<typeof ChainLinkIcon> = {
21
+ title: "Icons/ChainLinkIcon",
22
+ component: ChainLinkIcon,
23
+ parameters: {
24
+ layout: "fullscreen",
25
+ backgrounds: {
26
+ default: "dark",
27
+ values: [
28
+ { name: "dark", value: "var(--color-fm-surface-primary)" },
29
+ { name: "darker", value: "var(--color-fm-neutral-0)" },
30
+ { name: "light", value: "var(--color-fm-neutral-1100)" },
31
+ ],
32
+ },
33
+ docs: {
34
+ page: () => (
35
+ <AuralIconDocsPage
36
+ accentToken="info"
37
+ features={[
38
+ {
39
+ title: "Chain Link Shape",
40
+ description: "Two-link URL indicator",
41
+ },
42
+ { title: "Scalable", description: "Works at any size" },
43
+ { title: "Accessible", description: "ARIA-ready by default" },
44
+ ]}
45
+ quickStart={{
46
+ codeExample: `import { ChainLinkIcon } from "src/ui/icons/chain-link-icon"
47
+
48
+ function CopyLinkButton() {
49
+ return (
50
+ <button className="flex items-center gap-2">
51
+ <ChainLinkIcon className="h-4 w-4" />
52
+ Copy link
53
+ </button>
54
+ )
55
+ }`,
56
+ livePreview: (
57
+ <button className="bg-fm-surface-secondary border-fm-divider-secondary text-fm-primary font-fm-text flex items-center gap-2 rounded-xl border px-4 py-2.5 text-sm">
58
+ <ChainLinkIcon className="text-fm-icon-active h-4 w-4" />
59
+ Copy link
60
+ </button>
61
+ ),
62
+ }}
63
+ relatedIcons={[
64
+ {
65
+ name: "ShareIcon",
66
+ description: "Share content externally",
67
+ icon: ShareIcon,
68
+ accentToken: "info",
69
+ },
70
+ {
71
+ name: "CopyIcon",
72
+ description: "Duplicate to clipboard",
73
+ icon: CopyIcon,
74
+ accentToken: "positive",
75
+ },
76
+ {
77
+ name: "GlobeIcon",
78
+ description: "Web or URL context",
79
+ icon: GlobeIcon,
80
+ accentToken: "warning",
81
+ },
82
+ {
83
+ name: "ArrowRightUpIcon",
84
+ description: "Open in new tab",
85
+ icon: ArrowRightUpIcon,
86
+ accentToken: "negative",
87
+ },
88
+ ]}
89
+ />
90
+ ),
91
+ },
92
+ },
93
+ tags: ["autodocs"],
94
+ }
95
+
96
+ export default meta
97
+ type Story = StoryObj<typeof ChainLinkIcon>
98
+
99
+ export const Default: Story = {
100
+ render: (args) => (
101
+ <IconDefaultCanvas>
102
+ <ChainLinkIcon className="text-fm-icon-active h-5 w-5" {...args} />
103
+ </IconDefaultCanvas>
104
+ ),
105
+ }
106
+
107
+ export const SizeVariations: Story = {
108
+ render: () => <IconSizeVariations icon={ChainLinkIcon} />,
109
+ }
110
+
111
+ export const ColorVariations: Story = {
112
+ render: () => <IconColorVariations icon={ChainLinkIcon} />,
113
+ }
114
+
115
+ export const UsageExamples: Story = {
116
+ render: () => (
117
+ <IconUsageCanvas>
118
+ <IconUsageSection title="Copy Link Button">
119
+ <button className="bg-fm-surface-secondary border-fm-divider-secondary text-fm-primary font-fm-text flex items-center gap-2 rounded-xl border px-4 py-2.5 text-sm">
120
+ <ChainLinkIcon className="text-fm-icon-active h-4 w-4" />
121
+ Copy link
122
+ </button>
123
+ </IconUsageSection>
124
+
125
+ <IconUsageSection title="Inline Link Reference">
126
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary flex w-full max-w-sm items-center gap-3 rounded-xl border p-4">
127
+ <ChainLinkIcon className="text-fm-icon-info h-4 w-4 shrink-0" />
128
+ <span className="text-fm-secondary font-fm-text truncate text-sm">
129
+ https://pocketfm.com/show/audio-drama
130
+ </span>
131
+ </div>
132
+ </IconUsageSection>
133
+
134
+ <IconUsageSection title="Share Row">
135
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary w-full max-w-sm rounded-xl border p-4">
136
+ <p className="text-fm-secondary font-fm-text mb-3 text-xs tracking-wider uppercase">
137
+ Share via
138
+ </p>
139
+ <div className="flex flex-col gap-3">
140
+ {[
141
+ { label: "Copy link", icon: ChainLinkIcon },
142
+ { label: "Share externally", icon: ShareIcon },
143
+ ].map(({ label, icon: Icon }) => (
144
+ <button
145
+ key={label}
146
+ className="text-fm-primary font-fm-text flex items-center gap-3 text-sm"
147
+ >
148
+ <span className="bg-fm-surface-tertiary flex h-8 w-8 items-center justify-center rounded-lg">
149
+ <Icon className="text-fm-icon-active h-4 w-4" />
150
+ </span>
151
+ {label}
152
+ </button>
153
+ ))}
154
+ </div>
155
+ </div>
156
+ </IconUsageSection>
157
+ </IconUsageCanvas>
158
+ ),
159
+ }
160
+
161
+ export const Playground: Story = {
162
+ render: (args) => (
163
+ <IconPlaygroundCanvas>
164
+ <ChainLinkIcon className="text-fm-icon-active h-6 w-6" {...args} />
165
+ </IconPlaygroundCanvas>
166
+ ),
167
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react"
2
+ import { AccessibleIcon } from "@radix-ui/react-accessible-icon"
3
+
4
+ export interface IChainLinkIconProps extends React.SVGProps<SVGSVGElement> {
5
+ withAccessibility?: boolean
6
+ }
7
+
8
+ export const ChainLinkIcon = (props: IChainLinkIconProps) => {
9
+ const { withAccessibility = true, ...svgProps } = props
10
+
11
+ const svg = (
12
+ <svg
13
+ viewBox="0 0 16 16"
14
+ fill="none"
15
+ xmlns="http://www.w3.org/2000/svg"
16
+ stroke="currentColor"
17
+ strokeWidth="1.5"
18
+ strokeLinecap="square"
19
+ {...svgProps}
20
+ >
21
+ <path d="M6.5001 3.68247L7.14443 3.03814C8.75084 1.43172 11.3554 1.43172 12.9618 3.03814C14.5682 4.64456 14.5682 7.24907 12.9618 8.85548L12.316 9.50124M3.68582 6.49674L3.03806 7.1445C1.43165 8.75092 1.43165 11.3554 3.03806 12.9618C4.64448 14.5683 7.24899 14.5683 8.85541 12.9618L9.49843 12.3188M6.33325 9.66666L9.66658 6.33333" />
22
+ </svg>
23
+ )
24
+
25
+ if (withAccessibility) {
26
+ return <AccessibleIcon label="Chain link icon">{svg}</AccessibleIcon>
27
+ }
28
+
29
+ return svg
30
+ }
@@ -0,0 +1,8 @@
1
+ export const meta = {
2
+ dependencies: {
3
+ "@radix-ui/react-accessible-icon": "^1.1.7",
4
+ },
5
+ devDependencies: {},
6
+ internalDependencies: [],
7
+ tokens: [],
8
+ }