Skip to content

Commit

Permalink
feat(FilterChip): add FilterChip component
Browse files Browse the repository at this point in the history
* create FilterChip component

* add basic FilterChip styles

* fix tests

* create FilterChipImplementation story

* cleanup

* cleanup for Dropdown

* documentation cleanup
  • Loading branch information
jphechter authored Oct 20, 2022
1 parent 5fa4ce3 commit ff47b5c
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 146 deletions.
1 change: 1 addition & 0 deletions src/assets/theme/_uswds-theme-custom-styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@forward "../../components/Checkbox/Checkbox.scss";
@forward "../../components/Datefield/Datefield.scss";
@forward "../../components/Dropdown/Dropdown.scss";
@forward "../../components/FilterChip/FilterChip.scss";
@forward "../../components/Footer/Footer.scss";
@forward "../../components/Header/ActionsMenu.scss";
@forward "../../components/Header/Header.scss";
Expand Down
14 changes: 8 additions & 6 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,26 @@ const ButtonVariationConversion: { [key: string]: string } = {
};

export const Button: React.FC<Props> = ({
ariaLabel,
buttonVariation = "primary",
buttonText,
shiftIconLeft = false,
className,
disabled = false,
iconName,
ariaLabel,
largeButton = false,
disabled = false,
shiftIconLeft = false,
target = "_self",
...rest
}) => {
const buttonVariationType = ButtonVariationConversion[buttonVariation] ?? "";
const classNames = `display-flex usa-button usa-button--${buttonVariationType} ${
largeButton ? "usa-button--big" : ""
}${className ? ` ${className}` : ""}`;
return (
<button
disabled={disabled}
aria-label={ariaLabel || `${buttonText} button`}
className={`display-flex usa-button usa-button--${buttonVariationType} ${
largeButton ? "usa-button--big" : ""
}`}
className={classNames}
formTarget={target}
{...rest}
>
Expand Down
10 changes: 5 additions & 5 deletions src/components/Dropdown/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { Dropdown } from "./Dropdown";
import data from "./data.json";
Expand Down Expand Up @@ -38,10 +38,10 @@ export default {
},
} as ComponentMeta<typeof Dropdown>;

const Template: ComponentStory<typeof Dropdown> = ({ ...rest }) => (
<Dropdown {...rest} />
);

const Template: ComponentStory<typeof Dropdown> = ({ ...rest }) => {
const [value, setValue] = useState("");
return <Dropdown {...rest} value={value} setValue={setValue} />;
};
export const Default = Template.bind({});

Default.args = {
Expand Down
26 changes: 19 additions & 7 deletions src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Icon } from "components/Icon/Icon";
import React, { useEffect, useRef, useState } from "react";
import React, {
Dispatch,
SetStateAction,
useEffect,
useRef,
useState,
} from "react";

type IntrinsicElements = JSX.IntrinsicElements["select"];

Expand All @@ -12,6 +18,8 @@ interface Props extends IntrinsicElements {
data: DropdownData[];
label: string;
readOnly?: boolean;
setValue?: Dispatch<SetStateAction<any>>;
value?: string | number | undefined;
}

/**
Expand All @@ -34,6 +42,8 @@ export const Dropdown: React.FC<Props> = ({
label,
name,
readOnly = false,
setValue,
value,
...rest
}) => {
/*
Expand All @@ -49,7 +59,9 @@ export const Dropdown: React.FC<Props> = ({
const [dropdownData, setDropdownData] = useState<DropdownData[]>(data);
const [hidden, setHidden] = useState<boolean>(true);
const [inputValue, setInputValue] = useState<string>("");
const [value, setValue] = useState<string | number | undefined>(undefined);

if (value === undefined && setValue === undefined)
[value, setValue] = useState<string | number | undefined>("");

const closeDropdown = () => {
setHidden(true);
Expand Down Expand Up @@ -93,7 +105,7 @@ export const Dropdown: React.FC<Props> = ({

// triggered by click or click-equivalent event (Enter/Tab)
const handleItemClick = (itemValue: string | number) => {
setValue(itemValue);
setValue && setValue(itemValue);
closeDropdown();
};

Expand All @@ -117,7 +129,8 @@ export const Dropdown: React.FC<Props> = ({
// when value updates, inputValue should match value
useEffect(() => {
setInputValue(getDisplayString());
}, [value]);
setDropdownData(data);
}, [value, data]);

return (
<>
Expand All @@ -137,7 +150,7 @@ export const Dropdown: React.FC<Props> = ({
className ? ` ${className}` : ""
}`}
name={name}
onChange={(e) => setValue(e.target.value)}
onChange={(e) => setValue && setValue(e.target.value)}
tabIndex={-1}
value={value}
{...rest}
Expand Down Expand Up @@ -183,8 +196,7 @@ export const Dropdown: React.FC<Props> = ({
className="usa-combo-box__clear-input"
aria-label="Clear the select contents"
onClick={() => {
setDropdownData(data);
setValue("");
setValue && setValue("");
}}
>
<Icon name="close" />
Expand Down
17 changes: 17 additions & 0 deletions src/components/FilterChip/FilterChip.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.filter-chip {
padding: 3px 8px 5px;
line-height: unset;
span {
padding: 0;
}
div {
height: 100%;
padding-left: 10px;
svg {
display: inline-block;
vertical-align: middle;
height: 1rem;
width: 1rem;
}
}
}
31 changes: 31 additions & 0 deletions src/components/FilterChip/FilterChip.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { FilterChip } from "./FilterChip";

export default {
title: "COMPONENTS/FilterChip",
component: FilterChip,
args: {
text: "Non cras.",
},
argTypes: {
text: {
description: "Text content to be displayed within the FilterChip.",
},
},
parameters: {
docs: {
description: {
component:
"The FilterChip uses a tag or descriptive words as a way to filter content. This may be a good alternative to a Checkbox for larger data sets.\n\nThis component contains no actual logic to interact with your data. See the FilterChip Implementation story for an example of how this might be used.",
},
},
},
} as ComponentMeta<typeof FilterChip>;

const Template: ComponentStory<typeof FilterChip> = ({ ...rest }) => (
<FilterChip {...rest} />
);

export const Component = Template.bind({});
Component.args = {};
9 changes: 9 additions & 0 deletions src/components/FilterChip/FilterChip.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";
import { render } from "../../test-setup";
import { FilterChip } from "./FilterChip";

describe("Tests for the FilterChip component", () => {
it("should render", () => {
render(<FilterChip text="Standard Filter Chip" />);
});
});
25 changes: 25 additions & 0 deletions src/components/FilterChip/FilterChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Button } from "components/Button/Button";
import React from "react";

type IntrinsicElements = JSX.IntrinsicElements["button"];

interface Props extends IntrinsicElements {
text: string;
}

/**
* **FilterChip Component**
*
* @param {string} text Text content to be displayed within the FilterChip.
*/
export const FilterChip: React.FC<Props> = ({ text, ...rest }) => {
return (
<Button
buttonVariation="primary"
buttonText={text}
className={"filter-chip"}
iconName="close"
{...rest}
/>
);
};
66 changes: 66 additions & 0 deletions src/components/FilterChip/FilterChipImplementation.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { useEffect, useState } from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { FilterChip } from "./FilterChip";
import { Typography } from "../Typography/Typography";
import { Dropdown } from "../Dropdown/Dropdown";
import dropdownData from "../Dropdown/data.json";

export default {
title: "COMPONENTS/FilterChip",
} as ComponentMeta<typeof FilterChip>;

const Template: ComponentStory<any> = () => {
const [data, setDropdownData] = useState(dropdownData);
const [selectedValues, setSelectedValues] = useState<string[]>([]);
const [value, setValue] = useState<string | number | undefined>("");

const handleValueChange = (val) => {
const obj = dropdownData.find((itm) => itm.value === val);
const displayString = obj?.displayString;
if (displayString) setSelectedValues([...selectedValues, displayString]);
setValue("");
};

const removeChip = (val) => {
setSelectedValues(selectedValues.filter((e) => e !== val));
};

useEffect(() => {
setDropdownData(
dropdownData.filter(
(item) => !selectedValues.includes(item.displayString)
)
);
}, [selectedValues]);

return (
<>
<Typography>FilterChip Implementation Example</Typography>
<Dropdown
data={data}
label={"Select an item from the list:"}
value={value}
setValue={handleValueChange}
/>

{selectedValues.length > 0 && (
<Typography size="xs">Click a FilterChip to remove it.</Typography>
)}
<span style={{ display: "flex", flexDirection: "row" }}>
{selectedValues.map((val, idx) => (
<FilterChip
text={val}
key={`filterchip-${idx}`}
onClick={() => removeChip(val)}
/>
))}
</span>
</>
);
};

export const Implementation = Template.bind({});
Implementation.args = {};
Implementation.parameters = {
controls: { hideNoControlsWarning: true },
};
6 changes: 3 additions & 3 deletions src/components/Header/Header.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";

import { ActionsMenu } from "./ActionsMenu";
import { Button } from "components/Button/Button";
import { Link } from "components/Link/Link";
import { Logo } from "components/Logo/Logo";
import { Button } from "../Button/Button";
import { Link } from "../Link/Link";
import { Logo } from "../Logo/Logo";
import { Header } from "./Header";

import pngLogo from "../../assets/img/logos/cms_logo.png";
Expand Down
28 changes: 12 additions & 16 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useRef, useState } from "react";
import { Button } from "components/Button/Button";
import { Link, LinkProps } from "components/Link/Link";
import { Icon } from "../Icon/Icon";
import { Link, LinkProps } from "../Link/Link";
import { useOutsideClick } from "hooks/useOutsideClick";
import { useWindowDimensions } from "hooks/useWindowDimensions";

Expand Down Expand Up @@ -48,15 +48,16 @@ const NavSection: React.FC<NavSectionProps> = ({ section, index }) => {
return (
<>
<li className="usa-nav__primary-item" ref={wrapperRef}>
<Button
buttonText={buttonText}
<button
className={`usa-accordion__button usa-nav__link ${
current ? "usa-current" : ""
}`}
aria-expanded={expanded}
aria-controls={`extended-mega-nav-section-${index}`}
onClick={() => setExpanded(!expanded)}
/>
>
{buttonText}
</button>
<div
id={`extended-mega-nav-section-${index}`}
className="usa-nav__submenu usa-megamenu"
Expand Down Expand Up @@ -128,11 +129,9 @@ export const Header: React.FC<HeaderProps> = ({
<div className="usa-logo" id="basic-logo">
{headerLogo}
</div>
<Button
buttonText="Menu"
className="usa-menu-btn"
onClick={openMenu}
/>
<button className="usa-menu-btn" onClick={openMenu}>
Menu
</button>
{width >= 1024 && (
<div className="usa-nav__secondary usa-header--extended">
{secondaryComponent}
Expand All @@ -146,12 +145,9 @@ export const Header: React.FC<HeaderProps> = ({
{...rest}
>
<div className="usa-nav__inner">
<Button
buttonText=""
iconName="close"
className="usa-nav__close"
onClick={closeMenu}
/>
<button className="usa-nav__close" onClick={closeMenu}>
<Icon name="close" />
</button>
<ul className="usa-nav__primary usa-accordion">
{navData?.map((section, idx) => {
return (
Expand Down
Loading

0 comments on commit ff47b5c

Please sign in to comment.