Skip to content
This repository was archived by the owner on Sep 6, 2024. It is now read-only.
2 changes: 1 addition & 1 deletion packages/cdc-react-icons/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const Icons = {
Bell,
PageLayout,
PaperLines,
Person,
Plus,
ZigZag,
Star,
Expand All @@ -110,7 +111,6 @@ export const Icons = {
Swatch,
Target,
TrafficCone,
Person,
};

export type IconNames = keyof typeof Icons;
65 changes: 65 additions & 0 deletions packages/cdc-react/src/components/Dropdown/Dropdown.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
@use "../../scss/global.scss";
@use "uswds-core" as *;

.cdc-react {
.dropdown-label-wrap {
display: flex;
flex-direction: row;
max-width: units("card-lg");
align-items: center;
border: solid 0.1rem global.$gray-light;
cursor: pointer;
background: global.$gray-lighter;
border-radius: 0.2rem;

span {
display: flex;
color: global.$gray-darker;

&.dropdown-label-icon {
flex-grow: 0.05;
padding: units("05");
}

&.dropdown-label {
font-size: 0.875rem;
flex-grow: units("05");
}

&.dropdown-arrow {
flex: 1;
flex-grow: 1;
justify-content: end;
padding-right: units(1);
}
}
}

.dropdown-items {
display: none;
cursor: pointer;
flex-direction: column;
margin-top: units("neg-05");
max-width: units("card-lg");
box-shadow: 0 0 0.1rem 0.1rem #e7e6e6;
border-radius: 0.2rem;
position: relative;
background: #fff;
max-height: units("card");

&.show {
display: flex;
}

.dropdown-item {
&:hover {
background: #d5d5d5;
}
display: flex;
width: 100%;
padding: units(1);
border-radius: none;
font-size: 0.875rem;
}
}
}
36 changes: 36 additions & 0 deletions packages/cdc-react/src/components/Dropdown/Dropdown.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { render, screen } from "@testing-library/react";

import { vi } from "vitest";

import { Dropdown } from "..";
import { Icons } from "@us-gov-cdc/cdc-react-icons";

describe("Dropdown component", () => {
it("should render the Dropdown UI component and click actions work", () => {
const onSelect = vi.fn();

render(
<Dropdown
srText="Dropdown Screen Reader text"
label="Dropdown Label"
items={["Item One", "Item Two", "Item Three"]}
labelIcon={<Icons.Calendar />}
onSelect={onSelect}
/>
);

expect(screen.getByText("Dropdown Label")).toBeInTheDocument();

const dropdown = screen.getByText("Dropdown Label");

dropdown.click();

expect(screen.getByText("Item One")).toBeInTheDocument();

const dropdownItemOne = screen.getByText("Item One");

dropdownItemOne.click();

expect(onSelect).toHaveBeenCalled();
});
});
71 changes: 71 additions & 0 deletions packages/cdc-react/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import "./Dropdown.scss";

import { Icons } from "@us-gov-cdc/cdc-react-icons";
import { useState } from "react";

export interface DropdownProps {
label: string;
items: string[];
srText: string;
onSelect: (item: string) => void;
labelIcon?: React.ReactNode;
onKeyDownDropdownLabel?:
| React.KeyboardEventHandler<HTMLDivElement>
| undefined;
onKeyDownDropdownItems?:
| React.KeyboardEventHandler<HTMLDivElement>
| undefined;
id?: string;
}

/**
* Dropdown UI component
*/
export const Dropdown = ({
labelIcon,
label,
items = [],
srText,
onSelect,
onKeyDownDropdownLabel,
onKeyDownDropdownItems,
id,
}: DropdownProps) => {
const [dropdownOpen, setDropdownVisibility] = useState(false);
const [dropdownCurrentItem, setDropdownCurrentItem] = useState(label);

return (
<div className="cdc-react" id={id}>
<div className="dropdown">
<div
role="presentation"
onKeyDown={onKeyDownDropdownLabel}
onClick={() => setDropdownVisibility(!dropdownOpen)}
aria-label={srText}
className="dropdown-label-wrap">
<span className="dropdown-label-icon">{labelIcon}</span>
<span className="dropdown-label">{dropdownCurrentItem}</span>
<span className="dropdown-arrow">
{dropdownOpen ? <Icons.ChevronUp /> : <Icons.ChevronDown />}
</span>
</div>
<div className={`dropdown-items ${dropdownOpen && "show"}`}>
{items.map((item: string, index: number) => (
<div
role="presentation"
onKeyDown={onKeyDownDropdownItems}
onClick={() => {
setDropdownCurrentItem(item);
setDropdownVisibility(!dropdownOpen);
onSelect(item);
}}
className="dropdown-item"
key={`cdc-react-dropdown-option-${index}`}>
{item}
</div>
))}
</div>
</div>
</div>
);
};
1 change: 1 addition & 0 deletions packages/cdc-react/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from "./ProfileHeader/ProfileHeaderPopupMenuItem/ProfileHeaderPopupMenu
export * from "./ProfileHeader/ProfileHeaderUserProfileMenuItem/ProfileHeaderUserProfileMenuItem";
export * from "./Button/Button";
export * from "./Divider/Divider";
export * from "./Dropdown/Dropdown";
export * from "./Card/Card";

export * from "../@types";
1 change: 1 addition & 0 deletions packages/cdc-react/src/scss/_colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ $main-theme-lighter: #c0e9ff;
$gray-dark: #555555;
$gray-darker: #333333;
$gray-light: #e0e0e0;
$gray-lighter: #f0f0f0;

$primary-blue: #005eaa;
$primary-amber: #ffecb3; // TODO: get accurate color.
Expand Down
27 changes: 27 additions & 0 deletions packages/storybook/src/stories/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Dropdown } from "@us-gov-cdc/cdc-react";
import { Icons } from "@us-gov-cdc/cdc-react-icons";

const meta: Meta<typeof Dropdown> = {
title: "Components/Dropdown",
tags: ["autodocs"],
component: Dropdown,
argTypes: {},
decorators: [(Story) => <Story />],
};

export default meta;

type Story = StoryObj<typeof Dropdown>;

export const Example: Story = {
args: {
srText: "Dropdown Screen Reader text",
label: "Dropdown Label",
items: ["Item One", "Item Two", "Item Three"],
labelIcon: <Icons.Calendar />,
onSelect: (item: string) => {
console.log(item);
},
},
};