Skip to content

Commit

Permalink
UI kit: Button visual updates (#136)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpierre authored Apr 30, 2024
1 parent 5b5836c commit 4522def
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import { ButtonFixture } from "./shared";

export default function Fixture() {
return <ButtonFixture defaultMode="primary" />;
return <ButtonFixture size="large" />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import { ButtonFixture } from "./shared";

export default function Fixture() {
return <ButtonFixture defaultMode="positive" />;
return <ButtonFixture size="medium" />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import { ButtonFixture } from "./shared";

export default function Fixture() {
return <ButtonFixture defaultMode="negative" />;
return <ButtonFixture size="small" />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import { ButtonFixture } from "./shared";

export default function Fixture() {
return <ButtonFixture defaultMode="secondary" />;
return <ButtonFixture size="mini" />;
}
42 changes: 22 additions & 20 deletions frontend/uikit-gallery/src/Button/shared.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
"use client";

import { Button } from "@liquity2/uikit";
import { useFixtureInput, useFixtureSelect } from "react-cosmos/client";
import { useFixtureInput } from "react-cosmos/client";

const modes = ["primary", "secondary", "tertiary", "positive", "negative"] as const;

export function ButtonFixture({
defaultMode,
size,
}: {
defaultMode: "primary" | "secondary" | "positive" | "negative";
size: "mini" | "small" | "medium" | "large";
}) {
const [label] = useFixtureInput("label", "Button");
const [mode] = useFixtureSelect("mode", {
options: ["primary", "secondary", "positive", "negative"],
defaultValue: defaultMode,
});
const [size] = useFixtureSelect("size", {
options: ["medium", "large"],
defaultValue: "medium",
});
const [wide] = useFixtureInput("wide", false);
const [disabled] = useFixtureInput("disabled", false);
return (
<div
style={{
display: "flex",
justifyContent: "center",
width: 608,
padding: 16,
alignItems: "center",
flexDirection: wide ? "column" : "row",
flexWrap: "wrap",
width: wide && (size === "mini" || size === "small") ? 200 : wide ? 400 : 600,
padding: size === "large" ? 32 : 16,
gap: 16,
}}
>
<Button
label={label}
mode={mode}
size={size}
wide={wide}
/>
{modes.map((mode) => (
<Button
key={mode}
label={mode.charAt(0).toUpperCase() + mode.slice(1)}
mode={mode}
size={size}
wide={wide}
disabled={disabled}
/>
))}
</div>
);
}
85 changes: 61 additions & 24 deletions frontend/uikit/src/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ComponentPropsWithoutRef } from "react";

import { match } from "ts-pattern";
import { match, P } from "ts-pattern";
import { css } from "../../styled-system/css";
import { useTheme } from "../Theme/Theme";

Expand All @@ -12,66 +12,95 @@ export function Button({
mode = "secondary",
...props
}: ComponentPropsWithoutRef<"button"> & {
size?: "medium" | "large";
size?: "mini" | "small" | "medium" | "large";
label: string;
maxWidth?: number;
wide?: boolean;
mode?: "primary" | "secondary" | "positive" | "negative";
mode?: "primary" | "secondary" | "tertiary" | "positive" | "negative";
}) {
const { color } = useTheme();

const geometry = match(size)
.with("mini", () => ({
height: 26,
padding: "0 6px",
fontSize: 14,
borderRadius: 8,
}))
.with("small", () => ({
height: 34,
padding: "0 8px",
fontSize: 14,
borderRadius: 8,
}))
.with("medium", () => ({
height: 40,
padding: "0 16px",
fontSize: 14,
padding: "0 14px",
fontSize: 16,
borderRadius: 20,
}))
.with("large", () => ({
height: 74,
padding: "0 32px",
padding: "0 30px",
fontSize: 24,
borderRadius: 120,
}))
.exhaustive();

const colors = match(mode)
.with("secondary", () => ({
color: color("secondaryContent"),
background: color("secondary"),
}))
.with("primary", () => ({
color: color("accentContent"),
background: color("accent"),
"--color": color("accentContent"),
"--background": color("accent"),
"--backgroundHover": color("accentHint"),
"--backgroundPressed": color("accentActive"),
}))
.with("positive", () => ({
color: color("positiveContent"),
background: color("positive"),
.with(P.union("secondary", "tertiary"), (mode) => ({
"--color": color("secondaryContent"),
"--background": mode === "secondary" ? color("secondary") : "transparent",
"--backgroundHover": color("secondaryHint"),
"--backgroundPressed": color("secondaryActive"),
}))
.with("negative", () => ({
color: color("negativeContent"),
background: color("negative"),
"--color": color("negativeContent"),
"--background": color("negative"),
"--backgroundHover": color("negativeHint"),
"--backgroundPressed": color("negativeActive"),
}))
.with("positive", () => ({
"--color": color("positiveContent"),
"--background": color("positive"),
"--backgroundHover": color("positiveHint"),
"--backgroundPressed": color("positiveActive"),
}))
.exhaustive();

return (
<button
{...props}
className={css({
display: "flex",
alignItems: "center",
justifyContent: "center",
whiteSpace: "nowrap",
color: "white",
cursor: "pointer",
_active: {
_enabled: {
translate: "0 1px",
transition: "background 50ms",
_enabled: {
color: "var(--color)",
background: {
base: "var(--background)",
_hover: "var(--backgroundHover)",
_active: "var(--backgroundPressed)",
},
},
_disabled: {
background: "rain",
color: "disabledContent",
background: "disabledSurface",
cursor: "not-allowed",
border: "1px solid token(colors.disabledBorder)",
},
_active: {
_enabled: {
translate: "0 1px",
},
},
_focusVisible: {
outline: "2px solid token(colors.focused)",
Expand All @@ -83,8 +112,16 @@ export function Button({
...colors,
...geometry,
}}
{...props}
>
{label}
<span
style={{
// prevents a jump due to the border when the button gets disabled
padding: props.disabled ? 0 : "0 1px",
}}
>
{label}
</span>
</button>
);
}
25 changes: 21 additions & 4 deletions frontend/uikit/src/Theme/Theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,19 @@ export const colors = {
// The light theme, which is the only theme for now. These
// colors are meant to be used by components via useTheme(),
// so that the theme can be changed at runtime.

// Some notes about naming conventions:
// - "xContent" is the color used over a "x" background (text, icons or outlines).
// - "xHint" is the color used to hint that "x" is interactive (generally on hover).
// - "xActive" is the color used to indicate that "x" is being interacted with (generally on press).
// - "xSurface" is the color used for the surface of "x" (generally the background).
export const lightTheme = {
name: "light" as const,
colors: {
accent: "blue:500",
accentActive: "blue:600",
accentContent: "white",
accentHint: "blue:400",
background: "white",
content: "gray:950",
contentAlt: "gray:600",
Expand All @@ -105,14 +113,23 @@ export const lightTheme = {
fieldSurface: "gray:50",
focused: "blue:500",
interactive: "blue:950",
negative: "red:400",
negative: "red:500",
negativeActive: "red:600",
negativeContent: "white",
positive: "green:400",
negativeHint: "red:400",
positive: "green:500",
positiveActive: "green:600",
positiveContent: "white",
secondary: "blue:950",
secondaryContent: "white",
positiveHint: "green:400",
secondary: "blue:50",
secondaryActive: "blue:200",
secondaryContent: "blue:500",
secondaryHint: "blue:100",
selected: "blue:500",
warning: "yellow:400",
disabledBorder: "gray:200",
disabledContent: "gray:500",
disabledSurface: "gray:50",
} satisfies Record<string, keyof typeof colors>,
} as const;

Expand Down

0 comments on commit 4522def

Please sign in to comment.