-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Button component #63
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
9d85690
improve Button component and stories
Benaiah 8a92ee3
Button: forward click event
Benaiah 486f099
Button: allow passing a class
Benaiah 86cbfb2
Button: import USWDS styles globally
Benaiah 3f25879
Button: move component files into subfolder
Benaiah 3bdd92d
Button: add tests
Benaiah 42df906
Button: fix import
Benaiah 6bd7ff7
Button: set up new variants based on LDAF design system
Benaiah fd2c843
Button: add story for inverse variant
Benaiah dbeaaf9
Button: styling fixes
Benaiah ec86fae
Button: fix type error in tests
Benaiah 4a44b76
Button: move ButtonTest to a __tests__ directory
Benaiah 696f044
Button: fix import
Benaiah 46180cd
Button: use Storybook's default dark-background setting
Benaiah c111049
Button: remove bad import in stories
Benaiah bf176b2
Button: don't re-import styles
Benaiah 30bf9ae
Button: fix appearance of large button
Benaiah 053c48c
Button: make classes reactive to try to improve Storybook behavior
Benaiah a86fee7
Button: link to Figma page in Storybook
Benaiah 40e3909
Button: add TODO comment in styles
Benaiah 6b4f69f
Button: refactoring, tests, stories
Benaiah 80465d3
Button: make selectors more specific so CSS isn't order-dependent
Benaiah 6eec05a
Button: fix type error in tests
Benaiah 154a4c8
Button: remove unused import
Benaiah File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
@use "sass:map"; | ||
|
||
// TODO: replace with global color variables from src/variables.scss | ||
$colors: ( | ||
"primary": #0051ad, | ||
"primary-dark": #063c7a, | ||
"primary-darker": #00284d, | ||
"disabled": #c9c9c9, | ||
"disabled-dark": #adadad, | ||
"base-lightest": #f2f1f0, | ||
"base-light": #a9aeb1, | ||
"base-dark": #565c65, | ||
"base-darker": #3d4551, | ||
"gray-05": #f0f0f0, | ||
"primary-lightest": #d1e9ff, | ||
"text-only-hover": rgba(6, 60, 122, 16%), | ||
"text-only-active": rgba(6, 60, 122, 30%), | ||
"outline-inverse": #dcdee0, | ||
); | ||
|
||
.usa-button { | ||
width: auto; | ||
color: #fff; | ||
background: map.get($colors, "primary"); | ||
&:hover { | ||
background: map.get($colors, "primary-dark"); | ||
} | ||
&:active { | ||
background: map.get($colors, "primary-darker"); | ||
} | ||
&:disabled { | ||
background: map.get($colors, "disabled-dark"); | ||
} | ||
} | ||
|
||
.usa-button.usa-button--base { | ||
background: map.get($colors, "base-dark"); | ||
&:hover { | ||
background: map.get($colors, "base-dark"); | ||
} | ||
&:active { | ||
background: map.get($colors, "base-darker"); | ||
} | ||
} | ||
|
||
.usa-button.usa-button--inverse { | ||
color: map.get($colors, "primary"); | ||
background: #fff; | ||
&:hover { | ||
color: map.get($colors, "primary"); | ||
background: map.get($colors, "gray-05"); | ||
} | ||
&:active { | ||
color: map.get($colors, "primary"); | ||
background: map.get($colors, "primary-lightest"); | ||
} | ||
&:disabled { | ||
color: #fff; | ||
} | ||
} | ||
|
||
.usa-button.usa-button--text-only { | ||
color: map.get($colors, "primary"); | ||
background: none; | ||
&:hover { | ||
color: map.get($colors, "primary"); | ||
background: map.get($colors, "text-only-hover"); | ||
} | ||
&:active { | ||
color: map.get($colors, "primary"); | ||
background: map.get($colors, "text-only-active"); | ||
} | ||
&:disabled { | ||
background: none; | ||
color: map.get($colors, "disabled"); | ||
} | ||
} | ||
|
||
.usa-button.usa-button--outline { | ||
background: none; | ||
color: map.get($colors, "primary"); | ||
border: 2px solid map.get($colors, "primary"); | ||
box-shadow: none; | ||
&:hover { | ||
background: none; | ||
color: map.get($colors, "primary-dark"); | ||
border: 2px solid map.get($colors, "primary-dark"); | ||
box-shadow: none; | ||
} | ||
&:active { | ||
background: none; | ||
color: map.get($colors, "base-darker"); | ||
border: 2px solid map.get($colors, "base-darker"); | ||
box-shadow: none; | ||
} | ||
&:disabled { | ||
background: none; | ||
color: map.get($colors, "disabled"); | ||
border: 2px solid map.get($colors, "disabled"); | ||
} | ||
} | ||
|
||
.usa-button.usa-button--outline-inverse { | ||
color: map.get($colors, "outline-inverse"); | ||
border-color: map.get($colors, "base-light"); | ||
&:hover { | ||
color: map.get($colors, "base-lightest"); | ||
border-color: map.get($colors, "base-lightest"); | ||
} | ||
&:active { | ||
color: #fff; | ||
border-color: #fff; | ||
} | ||
&:disabled { | ||
color: map.get($colors, "disabled-dark"); | ||
border-color: map.get($colors, "disabled-dark"); | ||
} | ||
} | ||
|
||
.usa-button.usa-button--big { | ||
background: map.get($colors, "primary"); | ||
color: #fff; | ||
&:hover { | ||
background: map.get($colors, "primary-dark"); | ||
} | ||
&:active { | ||
background: map.get($colors, "primary-darker"); | ||
} | ||
&:disabled { | ||
background: map.get($colors, "disabled"); | ||
} | ||
} | ||
|
||
.usa-button.usa-button--big-inverse { | ||
background: #fff; | ||
color: map.get($colors, "primary"); | ||
&:hover { | ||
background: map.get($colors, "gray-05"); | ||
color: map.get($colors, "primary"); | ||
} | ||
&:active { | ||
background: map.get($colors, "primary-lightest"); | ||
color: map.get($colors, "primary"); | ||
} | ||
&:disabled { | ||
background: map.get($colors, "disabled-dark"); | ||
color: #fff; | ||
} | ||
} | ||
|
||
.usa-button.usa-button--unstyled { | ||
background: none; | ||
color: #005ea2; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<script lang="ts"> | ||
import "./Button.scss"; | ||
import classNames from "$lib/util/classNames"; | ||
import type { Variant, Type } from "./buttonOptions"; | ||
|
||
export let disabled = false; | ||
|
||
export let unstyled = false; | ||
|
||
export let variant: Variant = "primary"; | ||
|
||
const variantClassesDict: Record<Variant, string[]> = { | ||
primary: [], | ||
base: ["usa-button--base"], | ||
inverse: ["usa-button--inverse"], | ||
"text-only": ["usa-button--text-only"], | ||
outline: ["usa-button--outline"], | ||
"outline-inverse": ["usa-button--outline", "usa-button--outline-inverse"], | ||
big: ["usa-button--big"], | ||
"big-inverse": ["usa-button--big", "usa-button--big-inverse"], | ||
}; | ||
|
||
$: variantClasses = variantClassesDict[variant]; | ||
|
||
export let type: Type = "button"; | ||
|
||
let className = ""; | ||
export { className as class }; | ||
$: classes = classNames( | ||
"usa-button", | ||
...variantClasses, | ||
unstyled && "usa-button--unstyled", | ||
className | ||
); | ||
</script> | ||
|
||
<button {type} {disabled} aria-disabled={disabled} class={classes} on:click> | ||
<slot>Button</slot> | ||
</button> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import "@testing-library/jest-dom"; | ||
import { render, screen } from "@testing-library/svelte"; | ||
import userEvent from "@testing-library/user-event"; | ||
import { describe, it, expect, vi } from "vitest"; | ||
import type { Variant } from "./buttonOptions"; | ||
|
||
import ButtonTest from "./__tests__/ButtonTest.svelte"; | ||
|
||
describe("Button", () => { | ||
it("renders", async () => { | ||
render(ButtonTest, { slot: "Test Button" }); | ||
const button = screen.getByRole("button"); | ||
expect(button).toBeInTheDocument(); | ||
expect(button).toHaveTextContent("Test Button"); | ||
}); | ||
|
||
it("clicks", async () => { | ||
const { component } = render(ButtonTest); | ||
const onClick = vi.fn(); | ||
component.$on("click", onClick); | ||
await userEvent.click(screen.getByRole("button")); | ||
expect(onClick).toHaveBeenCalledOnce(); | ||
}); | ||
|
||
it("does not click when disabled", async () => { | ||
const { component } = render(ButtonTest, { disabled: true }); | ||
const onClick = vi.fn(); | ||
component.$on("click", onClick); | ||
await userEvent.click(screen.getByRole("button")); | ||
expect(onClick).not.toHaveBeenCalled(); | ||
}); | ||
|
||
type VariantAndClass = [Variant, string]; | ||
|
||
( | ||
[ | ||
["primary", "usa-button"], | ||
["base", "usa-button usa-button--base"], | ||
["inverse", "usa-button usa-button--inverse"], | ||
["text-only", "usa-button usa-button--text-only"], | ||
["outline", "usa-button usa-button--outline"], | ||
["outline-inverse", "usa-button usa-button--outline usa-button--outline-inverse"], | ||
["big", "usa-button usa-button--big"], | ||
["big-inverse", "usa-button usa-button--big usa-button--big-inverse"], | ||
] satisfies VariantAndClass[] | ||
) | ||
.flatMap(([variant, expectedClass]): [{ variant: Variant; unstyled?: boolean }, string][] => [ | ||
[{ variant }, expectedClass], | ||
[{ variant, unstyled: true }, `${expectedClass} usa-button--unstyled`], | ||
]) | ||
.forEach(([props, expectedClass]) => { | ||
it(`renders the variant ${props.variant}${ | ||
props.unstyled ? " unstyled" : "" | ||
} with the expected CSS classes`, () => { | ||
render(ButtonTest, props); | ||
expect(screen.getByRole("button")).toHaveAttribute("class", expectedClass); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<script lang="ts"> | ||
import type { ComponentProps } from "svelte"; | ||
import Button from "../Button.svelte"; | ||
|
||
type $$Props = ComponentProps<Button> & { | ||
slot?: string; | ||
}; | ||
|
||
export let slot: string | undefined = "Button"; | ||
</script> | ||
|
||
<Button {...$$restProps} on:click> | ||
{slot} | ||
</Button> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export const variants = [ | ||
"primary", | ||
"base", | ||
"inverse", | ||
"text-only", | ||
"outline", | ||
"outline-inverse", | ||
"big", | ||
"big-inverse", | ||
] as const; | ||
|
||
export type Variant = (typeof variants)[number]; | ||
|
||
export const types = ["button", "submit", "reset"] as const; | ||
|
||
export type Type = (typeof types)[number]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { default } from "./Button.svelte"; | ||
|
||
export * from "./buttonOptions"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import type { Meta, StoryObj } from "@storybook/svelte"; | ||
|
||
import Button, { variants, types } from "$lib/components/Button"; | ||
|
||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/svelte/writing-stories/introduction | ||
const meta = { | ||
title: "Components/Button", | ||
component: Button, | ||
tags: ["autodocs"], | ||
argTypes: { | ||
variant: { | ||
control: { type: "select" }, | ||
options: variants, | ||
}, | ||
type: { | ||
control: { type: "select" }, | ||
options: types, | ||
}, | ||
unstyled: { | ||
control: { type: "boolean" }, | ||
}, | ||
}, | ||
parameters: { | ||
design: { | ||
type: "figma", | ||
url: " https://www.figma.com/file/oGKbyCnCRRdNzLYbiags93/LDAF-Component-Library-USWDS-3.0.2?node-id=2196-3764&t=HICajhP8FIexorTH-4", | ||
}, | ||
}, | ||
} satisfies Meta<Button>; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
// More on writing stories with args: https://storybook.js.org/docs/7.0/svelte/writing-stories/args | ||
export const Primary: Story = { | ||
args: {}, | ||
}; | ||
|
||
export const Base: Story = { | ||
args: { | ||
variant: "base", | ||
}, | ||
}; | ||
|
||
export const Inverse: Story = { | ||
parameters: { | ||
backgrounds: { default: "dark" }, | ||
}, | ||
args: { | ||
variant: "inverse", | ||
}, | ||
}; | ||
|
||
export const TextOnly: Story = { | ||
args: { | ||
variant: "text-only", | ||
}, | ||
}; | ||
|
||
export const Outline: Story = { | ||
args: { | ||
variant: "outline", | ||
}, | ||
}; | ||
|
||
export const OutlineInverse: Story = { | ||
parameters: { | ||
backgrounds: { default: "dark" }, | ||
}, | ||
args: { | ||
variant: "outline-inverse", | ||
}, | ||
}; | ||
|
||
export const Big: Story = { | ||
args: { | ||
variant: "big", | ||
}, | ||
}; | ||
|
||
export const BigInverse: Story = { | ||
parameters: { | ||
backgrounds: { default: "dark" }, | ||
}, | ||
args: { | ||
variant: "big-inverse", | ||
}, | ||
}; | ||
|
||
export const Unstyled: Story = { | ||
args: { | ||
unstyled: true, | ||
}, | ||
}; | ||
|
||
export const BigUnstyled: Story = { | ||
args: { | ||
variant: "big", | ||
unstyled: true, | ||
}, | ||
}; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My site title PR defined the current color palette as global variables. That covers everything here except for the last 3 (and
gray-05
, although$grayscale-05
is extremely close if that's an acceptable substitution, cc @getpunched )If we like that approach, I can make a smaller PR to just get that in or do follow-up on this once #50 gets in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A follow-up PR to add that would be great! I've added a TODO comment indicating that we should do that.