@fpkit/acss 1.0.0-beta.1 → 2.0.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 +92 -0
- package/docs/README.md +325 -0
- package/docs/guides/accessibility.md +764 -0
- package/docs/guides/architecture.md +705 -0
- package/docs/guides/composition.md +688 -0
- package/docs/guides/css-variables.md +522 -0
- package/docs/guides/storybook.md +828 -0
- package/docs/guides/testing.md +817 -0
- package/docs/testing/focus-indicator-testing.md +437 -0
- package/libs/{chunk-7XPFW7CB.js → chunk-43TK2ICH.js} +2 -2
- package/libs/chunk-5PJYLVFY.cjs +17 -0
- package/libs/chunk-5PJYLVFY.cjs.map +1 -0
- package/libs/chunk-E4OSROCA.cjs +17 -0
- package/libs/chunk-E4OSROCA.cjs.map +1 -0
- package/libs/chunk-KVKQLRJG.js +10 -0
- package/libs/chunk-KVKQLRJG.js.map +1 -0
- package/libs/{chunk-QVW6W76L.cjs → chunk-MGPWZRBX.cjs} +3 -3
- package/libs/chunk-NNTBIHSD.js +8 -0
- package/libs/chunk-NNTBIHSD.js.map +1 -0
- package/libs/{chunk-X3JCTEPD.js → chunk-QKHPHMG2.js} +2 -2
- package/libs/{chunk-T4T6GWYQ.cjs → chunk-R7NLLZU2.cjs} +3 -3
- package/libs/{chunk-X5LGFCWG.js → chunk-UJAQVHWC.js} +3 -3
- package/libs/{chunk-DKTHCQ5P.cjs → chunk-X5RKCLDC.cjs} +3 -3
- package/libs/components/breadcrumbs/breadcrumb.cjs +5 -5
- package/libs/components/breadcrumbs/breadcrumb.d.cts +1 -1
- package/libs/components/breadcrumbs/breadcrumb.d.ts +1 -1
- package/libs/components/breadcrumbs/breadcrumb.js +2 -2
- package/libs/components/button.cjs +3 -3
- package/libs/components/button.d.cts +1 -1
- package/libs/components/button.d.ts +1 -1
- package/libs/components/button.js +1 -1
- package/libs/components/buttons/button.css +1 -1
- package/libs/components/buttons/button.css.map +1 -1
- package/libs/components/buttons/button.min.css +2 -2
- package/libs/components/dialog/dialog.cjs +4 -4
- package/libs/components/dialog/dialog.js +2 -2
- package/libs/components/icons/icon.d.cts +32 -32
- package/libs/components/icons/icon.d.ts +32 -32
- package/libs/components/link/link.cjs +11 -3
- package/libs/components/link/link.d.cts +131 -3
- package/libs/components/link/link.d.ts +131 -3
- package/libs/components/link/link.js +1 -1
- package/libs/components/list/list.css +1 -1
- package/libs/components/list/list.min.css +1 -1
- package/libs/components/modal.cjs +3 -3
- package/libs/components/modal.js +2 -2
- package/libs/hooks.cjs +3 -3
- package/libs/hooks.d.cts +1 -1
- package/libs/hooks.d.ts +1 -1
- package/libs/hooks.js +2 -2
- package/libs/index.cjs +12 -12
- package/libs/index.css +1 -1
- package/libs/index.css.map +1 -1
- package/libs/index.d.cts +237 -2
- package/libs/index.d.ts +237 -2
- package/libs/index.js +5 -5
- package/package.json +4 -3
- package/src/components/README.mdx +1 -1
- package/src/components/breadcrumbs/breadcrumb.test.tsx +1 -2
- package/src/components/buttons/README.mdx +19 -9
- package/src/components/buttons/button.scss +5 -0
- package/src/components/buttons/button.stories.tsx +8 -5
- package/src/components/buttons/button.tsx +19 -15
- package/src/components/cards/card.stories.tsx +1 -1
- package/src/components/details/details.stories.tsx +1 -1
- package/src/components/form/form.stories.tsx +1 -1
- package/src/components/form/input.stories.tsx +1 -1
- package/src/components/form/select.stories.tsx +1 -1
- package/src/components/heading/README.mdx +292 -0
- package/src/components/icons/icon.stories.tsx +1 -1
- package/src/components/link/link.stories.tsx +205 -8
- package/src/components/link/link.test.tsx +1 -1
- package/src/components/link/link.tsx +22 -0
- package/src/components/link/link.types.ts +11 -3
- package/src/components/list/list.scss +1 -1
- package/src/components/nav/nav.stories.tsx +1 -1
- package/src/components/ui.stories.tsx +53 -19
- package/src/docs/accessibility.mdx +484 -0
- package/src/docs/composition.mdx +549 -0
- package/src/docs/css-variables.mdx +380 -0
- package/src/docs/fpkit-developer.mdx +623 -0
- package/src/introduction.mdx +356 -0
- package/src/styles/buttons/button.css +4 -0
- package/src/styles/buttons/button.css.map +1 -1
- package/src/styles/index.css +9 -3
- package/src/styles/index.css.map +1 -1
- package/src/styles/list/list.css +1 -1
- package/src/styles/utilities/_disabled.scss +5 -4
- package/libs/chunk-33PNJ4LO.cjs +0 -15
- package/libs/chunk-33PNJ4LO.cjs.map +0 -1
- package/libs/chunk-GT77BX4L.cjs +0 -17
- package/libs/chunk-GT77BX4L.cjs.map +0 -1
- package/libs/chunk-OVWLQYMK.js +0 -10
- package/libs/chunk-OVWLQYMK.js.map +0 -1
- package/libs/chunk-UEPAWMDF.js +0 -8
- package/libs/chunk-UEPAWMDF.js.map +0 -1
- package/libs/link-5192f411.d.ts +0 -323
- /package/libs/{chunk-7XPFW7CB.js.map → chunk-43TK2ICH.js.map} +0 -0
- /package/libs/{chunk-QVW6W76L.cjs.map → chunk-MGPWZRBX.cjs.map} +0 -0
- /package/libs/{chunk-X3JCTEPD.js.map → chunk-QKHPHMG2.js.map} +0 -0
- /package/libs/{chunk-T4T6GWYQ.cjs.map → chunk-R7NLLZU2.cjs.map} +0 -0
- /package/libs/{chunk-X5LGFCWG.js.map → chunk-UJAQVHWC.js.map} +0 -0
- /package/libs/{chunk-DKTHCQ5P.cjs.map → chunk-X5RKCLDC.cjs.map} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
1
2
|
import { StoryObj, Meta } from "@storybook/react-vite";
|
|
2
3
|
import { within, expect, userEvent, fn } from "storybook/test";
|
|
3
4
|
import { useRef, useEffect } from "react";
|
|
4
|
-
import Link from "./link";
|
|
5
|
+
import Link, { IconLink, LinkButton } from "./link";
|
|
5
6
|
import type { LinkProps } from "./link.types";
|
|
6
7
|
import "../../styles/link/link.css";
|
|
7
8
|
|
|
@@ -41,8 +42,30 @@ const meta = {
|
|
|
41
42
|
description: {
|
|
42
43
|
component: `A semantic, accessible anchor component with enhanced security for external links and flexible styling variants.
|
|
43
44
|
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **Automatic Security**: External links (\`target="_blank"\`) automatically get \`rel="noopener noreferrer"\`
|
|
48
|
+
- **WCAG 2.1 AA Compliant**: Focus indicators, semantic HTML, keyboard navigation
|
|
49
|
+
- **Flexible Styling**: Text links, button-styled links, and pill variants
|
|
50
|
+
- **Performance**: Optional prefetch hints for faster navigation
|
|
51
|
+
- **Ref Forwarding**: Direct DOM access for focus management
|
|
52
|
+
- **Type-Safe**: Full TypeScript support with comprehensive prop types
|
|
53
|
+
|
|
54
|
+
## Exported Components
|
|
55
|
+
|
|
56
|
+
### Link (default)
|
|
57
|
+
Main component for creating semantic anchor elements with enhanced features.
|
|
58
|
+
|
|
59
|
+
### IconLink
|
|
60
|
+
Specialized component for icon-only or icon-with-text links.
|
|
61
|
+
|
|
62
|
+
### LinkButton
|
|
63
|
+
Convenience component for button-styled links.
|
|
64
|
+
|
|
44
65
|
## CSS Variables
|
|
45
66
|
|
|
67
|
+
All units use **rem** (not px). Base: 1rem = 16px.
|
|
68
|
+
|
|
46
69
|
### Typography & Color
|
|
47
70
|
- \`--link-color\`: Link text color (default: #085ab7)
|
|
48
71
|
- \`--link-fw\`: Font weight (default: 400)
|
|
@@ -50,32 +73,40 @@ const meta = {
|
|
|
50
73
|
|
|
51
74
|
### Text Decoration
|
|
52
75
|
- \`--link-decoration\`: Text decoration style (default: none, underline on hover/focus)
|
|
53
|
-
- \`--link-decoration-offset\`: Underline offset (default: 0.09375rem
|
|
54
|
-
- \`--link-decoration-thickness\`: Underline thickness (default: 0.0625rem
|
|
76
|
+
- \`--link-decoration-offset\`: Underline offset (default: 0.09375rem)
|
|
77
|
+
- \`--link-decoration-thickness\`: Underline thickness (default: 0.0625rem)
|
|
55
78
|
- \`--link-skip-ink\`: Text decoration skip ink (default: auto)
|
|
56
79
|
|
|
57
80
|
### Background & Border
|
|
58
81
|
- \`--link-bg\`: Background color (default: transparent)
|
|
59
82
|
- \`--link-radius\`: Border radius (default: 0.25rem, 99rem for pills)
|
|
60
83
|
|
|
61
|
-
### Spacing
|
|
84
|
+
### Spacing
|
|
62
85
|
- \`--link-padding-inline\`: Horizontal padding (default: 0, calculated for button variants)
|
|
63
86
|
- \`--link-padding-block\`: Vertical padding (default: 0, calculated for button variants)
|
|
64
87
|
|
|
65
88
|
### Focus Indicators (WCAG 2.4.7)
|
|
66
89
|
- \`--link-focus-color\`: Focus outline color (default: currentColor)
|
|
67
|
-
- \`--link-focus-width\`: Focus outline width (default: 0.125rem
|
|
68
|
-
- \`--link-focus-offset\`: Focus outline offset (default: 0.125rem
|
|
90
|
+
- \`--link-focus-width\`: Focus outline width (default: 0.125rem)
|
|
91
|
+
- \`--link-focus-offset\`: Focus outline offset (default: 0.125rem)
|
|
69
92
|
- \`--link-focus-style\`: Focus outline style (default: solid)
|
|
70
93
|
|
|
71
94
|
### Transitions
|
|
72
95
|
- \`--link-transition\`: Transition timing (default: all 0.75s ease-in-out)
|
|
73
96
|
|
|
74
|
-
### Button
|
|
97
|
+
### Button Variant Variables
|
|
98
|
+
Applied when using \`btnStyle\` prop, \`data-btn\` attribute, or \`<b>\`/\`<i>\` wrappers:
|
|
75
99
|
- \`--link-button-color\`: Button link text color (default: var(--link-color))
|
|
76
|
-
- \`--link-border-width\`: Button border width (default: 0.125rem
|
|
100
|
+
- \`--link-border-width\`: Button border width (default: 0.125rem)
|
|
77
101
|
- \`--link-border-color\`: Button border color (default: currentColor)
|
|
78
102
|
- \`--link-border-style\`: Button border style (default: solid)
|
|
103
|
+
|
|
104
|
+
## Usage Patterns
|
|
105
|
+
|
|
106
|
+
### Button Styling (3 ways)
|
|
107
|
+
1. **btnStyle prop**: \`<Link href="/signup" btnStyle="btn">Sign Up</Link>\`
|
|
108
|
+
2. **Wrapper elements**: \`<Link href="/signup"><b>Sign Up</b></Link>\` (button) or \`<Link href="/signup"><i>Sign Up</i></Link>\` (pill)
|
|
109
|
+
3. **Direct attribute**: \`<Link href="/signup" data-btn>Sign Up</Link>\`
|
|
79
110
|
`,
|
|
80
111
|
},
|
|
81
112
|
},
|
|
@@ -458,3 +489,169 @@ export const WithBothHandlers: Story = {
|
|
|
458
489
|
},
|
|
459
490
|
},
|
|
460
491
|
};
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Button-styled link using btnStyle prop.
|
|
495
|
+
* Demonstrates direct usage of the btnStyle prop instead of wrapper elements.
|
|
496
|
+
*/
|
|
497
|
+
export const WithBtnStyleProp: Story = {
|
|
498
|
+
args: {
|
|
499
|
+
href: "/dashboard",
|
|
500
|
+
btnStyle: "btn",
|
|
501
|
+
children: "Go to Dashboard",
|
|
502
|
+
},
|
|
503
|
+
parameters: {
|
|
504
|
+
docs: {
|
|
505
|
+
description: {
|
|
506
|
+
story:
|
|
507
|
+
"Use the `btnStyle` prop to apply button styling directly. This sets the `data-btn` attribute on the link element.",
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
|
|
512
|
+
const canvas = within(canvasElement);
|
|
513
|
+
const link = canvas.getByRole("link");
|
|
514
|
+
|
|
515
|
+
// Verify btnStyle creates data-btn attribute
|
|
516
|
+
expect(link).toHaveAttribute("data-btn", "btn");
|
|
517
|
+
expect(link).toHaveTextContent("Go to Dashboard");
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* IconLink component for icon-based navigation.
|
|
523
|
+
* Specialized component for links with icons.
|
|
524
|
+
*/
|
|
525
|
+
export const IconLinkComponent: Story = {
|
|
526
|
+
render: () => (
|
|
527
|
+
<IconLink
|
|
528
|
+
href="/home"
|
|
529
|
+
aria-label="Return to homepage"
|
|
530
|
+
icon={
|
|
531
|
+
<svg
|
|
532
|
+
aria-hidden="true"
|
|
533
|
+
width="20"
|
|
534
|
+
height="20"
|
|
535
|
+
viewBox="0 0 16 16"
|
|
536
|
+
fill="currentColor"
|
|
537
|
+
>
|
|
538
|
+
<path d="M8.707 1.5a1 1 0 0 0-1.414 0L.646 8.146a.5.5 0 0 0 .708.708L2 8.207V13.5A1.5 1.5 0 0 0 3.5 15h9a1.5 1.5 0 0 0 1.5-1.5V8.207l.646.647a.5.5 0 0 0 .708-.708L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.707 1.5ZM13 7.207V13.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V7.207l5-5 5 5Z" />
|
|
539
|
+
</svg>
|
|
540
|
+
}
|
|
541
|
+
>
|
|
542
|
+
{null}
|
|
543
|
+
</IconLink>
|
|
544
|
+
),
|
|
545
|
+
parameters: {
|
|
546
|
+
docs: {
|
|
547
|
+
description: {
|
|
548
|
+
story:
|
|
549
|
+
"The `IconLink` component is a convenience wrapper for icon-based links. Always include an `aria-label` for accessibility and set `aria-hidden='true'` on the icon.",
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
|
|
554
|
+
const canvas = within(canvasElement);
|
|
555
|
+
const link = canvas.getByRole("link");
|
|
556
|
+
|
|
557
|
+
// Verify accessible name
|
|
558
|
+
expect(link).toHaveAccessibleName("Return to homepage");
|
|
559
|
+
|
|
560
|
+
// Verify icon is hidden from screen readers
|
|
561
|
+
const svg = link.querySelector("svg");
|
|
562
|
+
expect(svg).toHaveAttribute("aria-hidden", "true");
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* IconLink with text label.
|
|
568
|
+
* Icon link with visible text alongside the icon.
|
|
569
|
+
*/
|
|
570
|
+
export const IconLinkWithText: Story = {
|
|
571
|
+
render: () => (
|
|
572
|
+
<IconLink
|
|
573
|
+
href="/downloads"
|
|
574
|
+
icon={
|
|
575
|
+
<svg
|
|
576
|
+
aria-hidden="true"
|
|
577
|
+
width="16"
|
|
578
|
+
height="16"
|
|
579
|
+
viewBox="0 0 16 16"
|
|
580
|
+
fill="currentColor"
|
|
581
|
+
style={{ marginRight: "0.5rem" }}
|
|
582
|
+
>
|
|
583
|
+
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
|
584
|
+
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z" />
|
|
585
|
+
</svg>
|
|
586
|
+
}
|
|
587
|
+
>
|
|
588
|
+
Download Files
|
|
589
|
+
</IconLink>
|
|
590
|
+
),
|
|
591
|
+
parameters: {
|
|
592
|
+
docs: {
|
|
593
|
+
description: {
|
|
594
|
+
story:
|
|
595
|
+
"IconLink can contain both an icon and text. The icon is passed via the `icon` prop, and text is provided as children.",
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* LinkButton component for call-to-action links.
|
|
603
|
+
* Convenience component that applies button styling automatically.
|
|
604
|
+
*/
|
|
605
|
+
export const LinkButtonComponent: Story = {
|
|
606
|
+
render: () => (
|
|
607
|
+
<LinkButton href="/get-started">
|
|
608
|
+
<b>Get Started Free</b>
|
|
609
|
+
</LinkButton>
|
|
610
|
+
),
|
|
611
|
+
parameters: {
|
|
612
|
+
docs: {
|
|
613
|
+
description: {
|
|
614
|
+
story:
|
|
615
|
+
"The `LinkButton` component is a convenience wrapper for button-styled links. It maintains semantic `<a>` element while providing button appearance.",
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
|
|
620
|
+
const canvas = within(canvasElement);
|
|
621
|
+
const link = canvas.getByRole("link");
|
|
622
|
+
|
|
623
|
+
// Verify it's still a semantic anchor
|
|
624
|
+
expect(link.tagName).toBe("A");
|
|
625
|
+
expect(link).toHaveAttribute("href", "/get-started");
|
|
626
|
+
expect(link).toHaveTextContent("Get Started Free");
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* LinkButton with custom styling.
|
|
632
|
+
* Demonstrates CSS variable overrides on LinkButton.
|
|
633
|
+
*/
|
|
634
|
+
export const LinkButtonCustom: Story = {
|
|
635
|
+
render: () => {
|
|
636
|
+
const customStyles: React.CSSProperties = {
|
|
637
|
+
"--link-button-color": "#ffffff",
|
|
638
|
+
"--link-bg": "#0066cc",
|
|
639
|
+
"--link-border-color": "#0066cc",
|
|
640
|
+
"--link-radius": "0.5rem",
|
|
641
|
+
} as React.CSSProperties;
|
|
642
|
+
|
|
643
|
+
return (
|
|
644
|
+
<LinkButton href="/signup" styles={customStyles}>
|
|
645
|
+
<b>Sign Up Now</b>
|
|
646
|
+
</LinkButton>
|
|
647
|
+
);
|
|
648
|
+
},
|
|
649
|
+
parameters: {
|
|
650
|
+
docs: {
|
|
651
|
+
description: {
|
|
652
|
+
story:
|
|
653
|
+
"LinkButton styling can be customized using CSS custom properties for colors, borders, and border radius.",
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
};
|
|
@@ -162,6 +162,28 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
|
|
|
162
162
|
}
|
|
163
163
|
);
|
|
164
164
|
|
|
165
|
+
export const IconLink = React.forwardRef<HTMLAnchorElement, LinkProps>(
|
|
166
|
+
({ href, icon, ...props }, ref) => {
|
|
167
|
+
return (
|
|
168
|
+
<Link ref={ref} href={href} {...props}>
|
|
169
|
+
{icon}
|
|
170
|
+
</Link>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
export const LinkButton = React.forwardRef<HTMLAnchorElement, LinkProps>(
|
|
176
|
+
({ href, children, ...props }, ref) => {
|
|
177
|
+
return (
|
|
178
|
+
<Link ref={ref} href={href} {...props}>
|
|
179
|
+
{children}
|
|
180
|
+
</Link>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
IconLink.displayName = "IconLink";
|
|
186
|
+
LinkButton.displayName = "LinkButton";
|
|
165
187
|
Link.displayName = "Link";
|
|
166
188
|
|
|
167
189
|
export default Link;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import UI from "#components/ui";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Props for the Link component.
|
|
@@ -233,8 +232,17 @@ export type LinkProps = {
|
|
|
233
232
|
* ```
|
|
234
233
|
*/
|
|
235
234
|
onPointerDown?: (event: React.PointerEvent<HTMLAnchorElement>) => void;
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Icon element to display in the link (used by IconLink).
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```tsx
|
|
241
|
+
* <IconLink href="/home" icon={<HomeIcon />} />
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
icon?: React.ReactNode;
|
|
245
|
+
} & Omit<React.ComponentPropsWithoutRef<"a">, 'style'>;
|
|
238
246
|
|
|
239
247
|
/**
|
|
240
248
|
* Props for the Link component with ref support.
|
|
@@ -18,7 +18,7 @@ dl {
|
|
|
18
18
|
--list-margin-top: 0;
|
|
19
19
|
--list-margin-bottom: 1rem; // 16px
|
|
20
20
|
--list-margin-inline: 0;
|
|
21
|
-
--list-padding-inline:
|
|
21
|
+
--list-padding-inline: 0.5rem; // 40px - default browser indent
|
|
22
22
|
--list-gap: 0.5rem; // 8px - gap between items
|
|
23
23
|
|
|
24
24
|
// List marker/bullet styling
|
|
@@ -15,7 +15,7 @@ import UI from "./ui";
|
|
|
15
15
|
* - Zero runtime overhead
|
|
16
16
|
*/
|
|
17
17
|
const meta = {
|
|
18
|
-
title: "FP
|
|
18
|
+
title: "FP/UI",
|
|
19
19
|
component: UI,
|
|
20
20
|
tags: ["autodocs", "primitive"],
|
|
21
21
|
parameters: {
|
|
@@ -782,7 +782,9 @@ export const CommonAccessibilityMistakes: Story = {
|
|
|
782
782
|
|
|
783
783
|
{/* Missing accessible name */}
|
|
784
784
|
<div>
|
|
785
|
-
<h4
|
|
785
|
+
<h4
|
|
786
|
+
style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}
|
|
787
|
+
>
|
|
786
788
|
❌ BAD: Icon button without accessible name
|
|
787
789
|
</h4>
|
|
788
790
|
<UI
|
|
@@ -800,15 +802,24 @@ export const CommonAccessibilityMistakes: Story = {
|
|
|
800
802
|
>
|
|
801
803
|
×
|
|
802
804
|
</UI>
|
|
803
|
-
<p
|
|
804
|
-
|
|
805
|
-
|
|
805
|
+
<p
|
|
806
|
+
style={{
|
|
807
|
+
fontSize: "0.875rem",
|
|
808
|
+
color: "#721c24",
|
|
809
|
+
marginTop: "0.5rem",
|
|
810
|
+
}}
|
|
811
|
+
>
|
|
812
|
+
<strong>Problem:</strong> Screen readers cannot identify this
|
|
813
|
+
button's purpose. <strong>Fix:</strong> Add{" "}
|
|
814
|
+
<code>aria-label="Close"</code>
|
|
806
815
|
</p>
|
|
807
816
|
</div>
|
|
808
817
|
|
|
809
818
|
{/* Non-semantic clickable div */}
|
|
810
819
|
<div>
|
|
811
|
-
<h4
|
|
820
|
+
<h4
|
|
821
|
+
style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}
|
|
822
|
+
>
|
|
812
823
|
❌ BAD: Clickable div without keyboard support
|
|
813
824
|
</h4>
|
|
814
825
|
<UI
|
|
@@ -825,16 +836,25 @@ export const CommonAccessibilityMistakes: Story = {
|
|
|
825
836
|
>
|
|
826
837
|
Click me (but you can't use keyboard!)
|
|
827
838
|
</UI>
|
|
828
|
-
<p
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
839
|
+
<p
|
|
840
|
+
style={{
|
|
841
|
+
fontSize: "0.875rem",
|
|
842
|
+
color: "#721c24",
|
|
843
|
+
marginTop: "0.5rem",
|
|
844
|
+
}}
|
|
845
|
+
>
|
|
846
|
+
<strong>Problem:</strong> Not keyboard accessible or announced to
|
|
847
|
+
screen readers. <strong>Fix:</strong> Use <code>as="button"</code>{" "}
|
|
848
|
+
or add <code>role="button"</code>, <code>tabIndex=0</code>, and
|
|
849
|
+
keyboard handlers.
|
|
832
850
|
</p>
|
|
833
851
|
</div>
|
|
834
852
|
|
|
835
853
|
{/* Poor contrast focus indicator */}
|
|
836
854
|
<div>
|
|
837
|
-
<h4
|
|
855
|
+
<h4
|
|
856
|
+
style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}
|
|
857
|
+
>
|
|
838
858
|
❌ BAD: Insufficient focus indicator contrast
|
|
839
859
|
</h4>
|
|
840
860
|
<UI
|
|
@@ -851,16 +871,24 @@ export const CommonAccessibilityMistakes: Story = {
|
|
|
851
871
|
>
|
|
852
872
|
Low contrast focus
|
|
853
873
|
</UI>
|
|
854
|
-
<p
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
874
|
+
<p
|
|
875
|
+
style={{
|
|
876
|
+
fontSize: "0.875rem",
|
|
877
|
+
color: "#721c24",
|
|
878
|
+
marginTop: "0.5rem",
|
|
879
|
+
}}
|
|
880
|
+
>
|
|
881
|
+
<strong>Problem:</strong> Focus indicator contrast ratio is less
|
|
882
|
+
than 3:1 (WCAG 2.4.7). <strong>Fix:</strong> Use a contrasting color
|
|
883
|
+
like dark blue on light blue background.
|
|
858
884
|
</p>
|
|
859
885
|
</div>
|
|
860
886
|
|
|
861
887
|
{/* Vague link text */}
|
|
862
888
|
<div>
|
|
863
|
-
<h4
|
|
889
|
+
<h4
|
|
890
|
+
style={{ marginTop: 0, marginBottom: "0.5rem", color: "#dc3545" }}
|
|
891
|
+
>
|
|
864
892
|
❌ BAD: Non-descriptive link text
|
|
865
893
|
</h4>
|
|
866
894
|
<UI
|
|
@@ -873,10 +901,16 @@ export const CommonAccessibilityMistakes: Story = {
|
|
|
873
901
|
>
|
|
874
902
|
Click here
|
|
875
903
|
</UI>
|
|
876
|
-
<p
|
|
904
|
+
<p
|
|
905
|
+
style={{
|
|
906
|
+
fontSize: "0.875rem",
|
|
907
|
+
color: "#721c24",
|
|
908
|
+
marginTop: "0.5rem",
|
|
909
|
+
}}
|
|
910
|
+
>
|
|
877
911
|
<strong>Problem:</strong> "Click here" doesn't describe the link's
|
|
878
|
-
destination. <strong>Fix:</strong> Use descriptive text like "View
|
|
879
|
-
documentation".
|
|
912
|
+
destination. <strong>Fix:</strong> Use descriptive text like "View
|
|
913
|
+
product documentation".
|
|
880
914
|
</p>
|
|
881
915
|
</div>
|
|
882
916
|
</div>
|