Skip to content

Commit 05aa322

Browse files
author
Ky Decker
authored
Add Namesake Directory (#429)
1 parent f2ba14c commit 05aa322

31 files changed

Lines changed: 1161 additions & 24 deletions

e2e/accessibility.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const paths = [
77
"/about",
88
"/blog",
99
"/brand-assets",
10+
"/directory",
1011
"/forms",
1112
"/guides",
1213
"/press",

sanity.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { structureTool } from "sanity/structure";
33
import {
44
authorType,
55
categoryType,
6+
contactType,
67
formType,
78
guideType,
89
pageType,
@@ -22,6 +23,7 @@ export default defineConfig({
2223
types: [
2324
authorType,
2425
categoryType,
26+
contactType,
2527
formType,
2628
guideType,
2729
pageType,

src/components/react/common/Button/Button.css

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.namesake-button {
1+
.react-aria-Button {
22
display: inline-flex;
33
gap: var(--space-s);
44
align-items: center;
@@ -9,6 +9,7 @@
99
font-weight: var(--weight-bold);
1010
color: var(--text-color);
1111
text-align: center;
12+
text-wrap: nowrap;
1213
text-decoration: none;
1314
appearance: none;
1415
outline: none;
@@ -22,6 +23,7 @@
2223
&:hover {
2324
transform: scale(1.05);
2425
}
26+
2527
&:active {
2628
transform: scale(0.98);
2729
}
@@ -55,8 +57,8 @@
5557
cursor: pointer;
5658
}
5759

58-
.namesake-button-start-icon:has(~ *),
59-
.namesake-button-end-icon:has(~ *) {
60+
.react-aria-Button-start-icon:has(~ *),
61+
.react-aria-Button-end-icon:has(~ *) {
6062
flex-shrink: 0;
6163
margin-inline: -0.2em;
6264
}

src/components/react/common/Button/Button.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe("Button", () => {
3636
<Button icon={RiArrowLeftLine}>Back</Button>,
3737
);
3838
expect(
39-
container.querySelector(".namesake-button-start-icon"),
39+
container.querySelector(".react-aria-Button-start-icon"),
4040
).toBeInTheDocument();
4141
});
4242

@@ -45,7 +45,7 @@ describe("Button", () => {
4545
<Button endIcon={RiArrowRightLine}>Next</Button>,
4646
);
4747
expect(
48-
container.querySelector(".namesake-button-end-icon"),
48+
container.querySelector(".react-aria-Button-end-icon"),
4949
).toBeInTheDocument();
5050
});
5151
});
@@ -69,7 +69,7 @@ describe("Button", () => {
6969
</Button>,
7070
);
7171
expect(
72-
container.querySelector(".namesake-button-start-icon"),
72+
container.querySelector(".react-aria-Button-start-icon"),
7373
).not.toBeInTheDocument();
7474
});
7575
});

src/components/react/common/Button/Button.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ export function Button({
3030
data-variant={variant}
3131
data-size={size}
3232
{...props}
33-
className={clsx("namesake-button", className)}
33+
className={clsx("react-aria-Button", className)}
3434
>
3535
{composeRenderProps(props.children, (children, { isPending }) => (
3636
<>
3737
{!isPending && Icon && (
38-
<Icon className="namesake-button-start-icon" size={iconSize} />
38+
<Icon className="react-aria-Button-start-icon" size={iconSize} />
3939
)}
4040
{isPending && (
4141
<ProgressCircle
@@ -46,7 +46,7 @@ export function Button({
4646
)}
4747
{children}
4848
{EndIcon && (
49-
<EndIcon className="namesake-button-end-icon" size={iconSize} />
49+
<EndIcon className="react-aria-Button-end-icon" size={iconSize} />
5050
)}
5151
</>
5252
))}

src/components/react/common/Calendar/Calendar.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
}
1717
}
1818

19-
.namesake-button[slot="previous"],
20-
.namesake-button[slot="next"] {
19+
.react-aria-Button[slot="previous"],
20+
.react-aria-Button[slot="next"] {
2121
width: var(--space-2xl);
2222
height: var(--space-2xl);
2323
padding: 0;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { render, screen } from "@testing-library/react";
2+
import userEvent from "@testing-library/user-event";
3+
import { describe, expect, it, vi } from "vitest";
4+
import { NativeOption, NativeSelect } from "./NativeSelect";
5+
6+
describe("NativeOption", () => {
7+
it("renders an option with value and text", () => {
8+
render(
9+
<select aria-label="Test">
10+
<NativeOption value="a">Alpha</NativeOption>
11+
</select>,
12+
);
13+
expect(screen.getByRole("option", { name: "Alpha" })).toHaveValue("a");
14+
});
15+
});
16+
17+
describe("NativeSelect", () => {
18+
it("renders options as children", () => {
19+
render(
20+
<NativeSelect aria-label="Pick one">
21+
<NativeOption value="1">One</NativeOption>
22+
<NativeOption value="2">Two</NativeOption>
23+
</NativeSelect>,
24+
);
25+
expect(
26+
screen.getByRole("combobox", { name: "Pick one" }),
27+
).toBeInTheDocument();
28+
expect(screen.getByRole("option", { name: "One" })).toHaveValue("1");
29+
expect(screen.getByRole("option", { name: "Two" })).toHaveValue("2");
30+
});
31+
32+
it("renders a visible label linked to the select", () => {
33+
render(
34+
<NativeSelect label="Country">
35+
<NativeOption value="us">United States</NativeOption>
36+
</NativeSelect>,
37+
);
38+
const label = screen.getByText("Country");
39+
const select = screen.getByRole("combobox", { name: "Country" });
40+
expect(label).toHaveAttribute("for", select.id);
41+
});
42+
43+
it("uses a stable id when id prop is passed", () => {
44+
render(
45+
<NativeSelect id="country-select" label="Country">
46+
<NativeOption value="us">United States</NativeOption>
47+
</NativeSelect>,
48+
);
49+
expect(screen.getByRole("combobox")).toHaveAttribute(
50+
"id",
51+
"country-select",
52+
);
53+
expect(screen.getByText("Country")).toHaveAttribute(
54+
"for",
55+
"country-select",
56+
);
57+
});
58+
59+
it("forwards extra props to the select", async () => {
60+
const user = userEvent.setup();
61+
const onChange = vi.fn();
62+
render(
63+
<NativeSelect aria-label="Test" onChange={onChange} data-testid="sel">
64+
<NativeOption value="a">A</NativeOption>
65+
<NativeOption value="b">B</NativeOption>
66+
</NativeSelect>,
67+
);
68+
expect(screen.getByTestId("sel")).toBeInTheDocument();
69+
await user.selectOptions(screen.getByTestId("sel"), "b");
70+
expect(onChange).toHaveBeenCalled();
71+
});
72+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import clsx from "clsx";
2+
import {
3+
type ComponentPropsWithoutRef,
4+
forwardRef,
5+
type SelectHTMLAttributes,
6+
useId,
7+
} from "react";
8+
import "../Button/Button.css";
9+
import "../Select/Select.css";
10+
import { Label } from "../Form";
11+
12+
export type NativeOptionProps = ComponentPropsWithoutRef<"option">;
13+
14+
export const NativeOption = forwardRef<HTMLOptionElement, NativeOptionProps>(
15+
function NativeOption(props, ref) {
16+
return <option ref={ref} {...props} />;
17+
},
18+
);
19+
20+
export interface NativeSelectProps
21+
extends SelectHTMLAttributes<HTMLSelectElement> {
22+
/** Rendered above the select as `<label htmlFor>`. */
23+
label?: string;
24+
}
25+
26+
export const NativeSelect = forwardRef<HTMLSelectElement, NativeSelectProps>(
27+
function NativeSelect({ label, className, id, children, ...props }, ref) {
28+
const genId = useId();
29+
const selectId = id ?? genId;
30+
31+
return (
32+
<div className={clsx("react-aria-Select", className)}>
33+
{label ? <Label htmlFor={selectId}>{label}</Label> : null}
34+
<select
35+
ref={ref}
36+
className={clsx("react-aria-Button", "react-aria-NativeSelect")}
37+
id={selectId}
38+
{...props}
39+
>
40+
{children}
41+
</select>
42+
</div>
43+
);
44+
},
45+
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./NativeSelect";

src/components/react/common/NumberField/NumberField.css

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
border-radius: 0;
1414
}
1515

16-
.namesake-button {
16+
.react-aria-Button {
1717
display: inline-flex;
1818
align-items: center;
1919
justify-content: center;
@@ -35,6 +35,7 @@
3535
&:hover {
3636
transform: none;
3737
}
38+
3839
&:active {
3940
transform: none;
4041
}
@@ -48,22 +49,23 @@
4849

4950
&[data-focus-within] {
5051
outline: 1px solid var(--focus-ring-color);
52+
5153
.react-aria-Input,
52-
.namesake-button {
54+
.react-aria-Button {
5355
border-color: var(--focus-ring-color);
5456
}
5557
}
5658
}
5759

5860
&[data-invalid] {
5961
.react-aria-Input,
60-
.namesake-button {
62+
.react-aria-Button {
6163
border-color: var(--invalid-color);
6264
}
6365

6466
&:focus-within {
6567
.react-aria-Input,
66-
.namesake-button {
68+
.react-aria-Button {
6769
border-color: var(--focus-ring-color);
6870
}
6971
}
@@ -80,7 +82,7 @@
8082
font-size: var(--step--2);
8183
}
8284

83-
.namesake-button {
85+
.react-aria-Button {
8486
&[data-disabled] {
8587
color: var(--text-color-disabled);
8688
border-color: var(--border-color-disabled);

0 commit comments

Comments
 (0)