Merge pull request 'UI: new Loader component; Button and Icon v2' (#3908) from ui/button into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3908
This commit is contained in:
brianmcgee
2025-06-12 15:52:17 +00:00
46 changed files with 4244 additions and 364 deletions

View File

@@ -27,5 +27,5 @@
}
.button--dark-active:active {
@apply active:border-secondary-900 active:shadow-inner-primary-active;
@apply active:border-secondary-900 active:shadow-button-primary-active;
}

View File

@@ -7,5 +7,5 @@
}
.button--ghost-active:active {
@apply active:bg-secondary-200 active:text-secondary-900 active:shadow-inner-primary-active;
@apply active:bg-secondary-200 active:text-secondary-900 active:shadow-button-primary-active;
}

View File

@@ -27,7 +27,7 @@
}
.button--light-active:active {
@apply active:bg-secondary-200 border-secondary-600 active:text-secondary-900 active:shadow-inner-primary-active;
@apply active:bg-secondary-200 border-secondary-600 active:text-secondary-900 active:shadow-button-primary-active;
box-shadow: inset 2px 2px theme(backgroundColor.secondary.300);

View File

@@ -1,36 +0,0 @@
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { TagList, TagListProps } from "./TagList";
const meta: Meta<TagListProps> = {
title: "Components/TagList",
component: TagList,
decorators: [
// wrap in a fixed width div so we can check that it wraps
(Story) => {
return (
<div style={{ width: "20em" }}>
<Story />
</div>
);
},
],
};
export default meta;
type Story = StoryObj<TagListProps>;
export const Default: Story = {
args: {
values: [
"Titan",
"Enceladus",
"Mimas",
"Dione",
"Iapetus",
"Tethys",
"Hyperion",
"Epimetheus",
],
},
};

View File

@@ -1,32 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Components/TagList Default smoke-test 1`] = `
<div style="width: 20em;">
<div class="tag-list">
<span class="fnt-clr-primary fnt-clr--inverted fnt-label-s fnt-weight-normal tag">
Titan
</span>
<span class="fnt-clr-primary fnt-clr--inverted fnt-label-s fnt-weight-normal tag">
Enceladus
</span>
<span class="fnt-clr-primary fnt-clr--inverted fnt-label-s fnt-weight-normal tag">
Mimas
</span>
<span class="fnt-clr-primary fnt-clr--inverted fnt-label-s fnt-weight-normal tag">
Dione
</span>
<span class="fnt-clr-primary fnt-clr--inverted fnt-label-s fnt-weight-normal tag">
Iapetus
</span>
<span class="fnt-clr-primary fnt-clr--inverted fnt-label-s fnt-weight-normal tag">
Tethys
</span>
<span class="fnt-clr-primary fnt-clr--inverted fnt-label-s fnt-weight-normal tag">
Hyperion
</span>
<span class="fnt-clr-primary fnt-clr--inverted fnt-label-s fnt-weight-normal tag">
Epimetheus
</span>
</div>
</div>
`;

View File

@@ -33,7 +33,7 @@ import Search from "@/icons/search.svg";
import Settings from "@/icons/settings.svg";
import Trash from "@/icons/trash.svg";
import Update from "@/icons/update.svg";
import Warning from "@/icons/warning.svg";
import Warning from "@/icons/warning-filled.svg";
const icons = {
ArrowBottom,

View File

@@ -33,6 +33,7 @@ export const InputBase = (props: InputBaseProps) => {
const [internal, inputProps] = splitProps(props, ["class", "divRef"]);
return (
<div
// eslint-disable-next-line tailwindcss/no-custom-classname
class={cx(
// Layout
"flex px-2 py-[0.375rem] flex-shrink-0 items-center justify-center gap-2 text-sm leading-6",
@@ -58,6 +59,7 @@ export const InputBase = (props: InputBaseProps) => {
props.class,
)}
classList={{
// eslint-disable-next-line tailwindcss/no-custom-classname
[cx("!border !border-semantic-1 !outline-semantic-1")]: !!props.error,
}}
aria-invalid={props.error}

View File

@@ -0,0 +1,134 @@
.button {
@apply flex gap-2 shrink-0 items-center justify-center;
@apply px-4 py-2;
height: theme(height.9);
border-radius: 3px;
/* Add transition for smooth width animation */
transition: width 0.5s ease 0.1s;
&.s {
@apply px-3 py-1.5;
height: theme(height.7);
border-radius: 2px;
&:has(> .icon-start):has(> .label) {
@apply pl-2;
}
&:has(> .icon-end):has(> .label) {
@apply pr-2;
}
}
&.primary {
@apply bg-inv-acc-4 fg-inv-1;
@apply border border-solid border-inv-4;
@apply shadow-button-primary;
&.ghost {
@apply bg-transparent border-none shadow-none;
}
&:hover {
@apply bg-inv-acc-3 border-solid shadow-button-primary-hover;
border-color: theme(backgroundColor.secondary.700);
}
&:active {
@apply bg-inv-acc-4 border-solid border-inv-3 shadow-button-primary-active;
}
&:focus-visible {
@apply bg-inv-acc-4 border-solid border-inv-3 shadow-button-primary-focus;
}
&:disabled {
@apply bg-def-acc-3 border-solid border-def-3 fg-def-3 shadow-none;
}
& > .icon {
@apply fg-inv-1;
}
}
&.secondary {
@apply bg-def-acc-2 fg-def-1;
@apply border border-solid border-inv-2;
@apply shadow-button-secondary;
&.ghost {
@apply bg-transparent border-none shadow-none;
}
&:hover {
@apply bg-def-acc-3 border-solid shadow-button-secondary-hover;
border-color: theme(backgroundColor.secondary.700);
}
&:focus-visible {
@apply bg-def-acc-3 border-solid border-inv-3 shadow-button-secondary-focus;
}
&:active {
@apply bg-def-acc-3 border-solid border-inv-4 shadow-button-secondary-active;
}
&:disabled {
@apply bg-def-2 border-solid border-def-2 fg-def-3 shadow-none;
}
& > .icon {
@apply fg-def-1;
&.icon-loading {
color: #0051ff;
}
}
}
&.icon {
@apply p-2;
}
&:has(> .icon-start):has(> .label) {
@apply pl-3.5;
}
&:has(> .icon-end):has(> .label) {
@apply pr-3.5;
}
& > div.loader {
@apply w-0 opacity-0;
@apply top-0 left-0 -mr-2;
transition: all 0.5s ease;
}
&.loading {
@apply cursor-wait;
& > div.loader {
@apply w-4 opacity-100;
margin-right: revert;
transition: all 0.5s ease;
}
}
}
/* button group */
.button-group .button:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.button-group .button:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.button-group .button:last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}

View File

@@ -0,0 +1,43 @@
import { DocsStory, Meta, Canvas } from "@storybook/blocks";
import * as ButtonStories from "./Button.stories";
<Meta of={ButtonStories} />
# Button
Buttons have a simple hierarchy, `primary` or `secondary`, and two sizes, `default` and `s`.
A `Button` can also have a label with a `startIcon`, an `endIcon` or both:
```tsx
<Button hierarchy="primary">Label</>
<Button hierarchy="secondary" size="s">Label</>
<Button hierarchy="primary" startIcon="Flash">Label</>
<Button hierarchy="primary" size="s" endIcon="Flash">Label</>
<Button hierarchy="primary" startIcon="Flash" endIcon="Flash">Label</>
```
To create a `Button` which is just an icon:
```tsx
<Button icon="Flash"/>
<Button icon="User" size="s"/>
```
<DocsStory of={ButtonStories.Primary} />
<DocsStory of={ButtonStories.Secondary} />
## Ghost Mode
Buttons in ghost mode have all the same states as normal buttons, except for their initial state has no background,
outline or box shadow.
```tsx
<Button hierarchy="primary" ghost={true}>Label</>
```
<DocsStory of={ButtonStories.GhostPrimary} />
<DocsStory of={ButtonStories.GhostSecondary} />

View File

@@ -0,0 +1,251 @@
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Button, ButtonProps } from "./Button";
import { Component } from "solid-js";
import { expect, fn, userEvent, waitFor, within } from "@storybook/test";
const getCursorStyle = (el: Element) => window.getComputedStyle(el).cursor;
const ButtonExamples: Component<ButtonProps> = (props) => (
<>
<div class="grid w-fit grid-cols-4 gap-8">
<div>
<Button data-testid="default" {...props}>
Label
</Button>
</div>
<div>
<Button data-testid="small" size="s" {...props}>
Label
</Button>
</div>
<div>
<Button data-testid="default-disabled" {...props} disabled={true}>
Disabled
</Button>
</div>
<div>
<Button
data-testid="small-disabled"
{...props}
disabled={true}
size="s"
>
Disabled
</Button>
</div>
<div>
<Button data-testid="default-start-icon" {...props} startIcon="Flash">
Label
</Button>
</div>
<div>
<Button
data-testid="small-start-icon"
{...props}
startIcon="Flash"
size="s"
>
Label
</Button>
</div>
<div>
<Button
data-testid="default-disabled-start-icon"
{...props}
startIcon="Flash"
disabled={true}
>
Disabled
</Button>
</div>
<div>
<Button
data-testid="small-disabled-start-icon"
{...props}
startIcon="Flash"
size="s"
disabled={true}
>
Disabled
</Button>
</div>
<div>
<Button data-testid="default-end-icon" {...props} endIcon="Flash">
Label
</Button>
</div>
<div>
<Button
data-testid="small-end-icon"
{...props}
endIcon="Flash"
size="s"
>
Label
</Button>
</div>
<div>
<Button
data-testid="default-disabled-end-icon"
{...props}
endIcon="Flash"
disabled={true}
>
Disabled
</Button>
</div>
<div>
<Button
data-testid="small-disabled-end-icon"
{...props}
endIcon="Flash"
size="s"
disabled={true}
>
Disabled
</Button>
</div>
<div>
<Button data-testid="default-icon" {...props} icon="Flash" />
</div>
<div>
<Button data-testid="small-icon" {...props} icon="Flash" size="s" />
</div>
<div>
<Button
data-testid="default-disabled-icon"
{...props}
icon="Flash"
disabled={true}
/>
</div>
<div>
<Button
data-testid="small-disabled-icon"
{...props}
icon="Flash"
disabled={true}
size="s"
/>
</div>
</div>
</>
);
const meta: Meta<ButtonProps> = {
title: "Components/Button",
component: ButtonExamples,
};
export default meta;
type Story = StoryObj<ButtonProps>;
export const Primary: Story = {
args: {
hierarchy: "primary",
onAction: fn(async () => {
// wait 500 ms to simulate an action
await new Promise((resolve) => setTimeout(resolve, 500));
// randomly fail to check that the loading state still returns to normal
if (Math.random() > 0.5) {
throw new Error("Action failure");
}
}),
},
parameters: {
test: {
mockTimers: true,
},
},
play: async ({ args, canvasElement, step }) => {
const canvas = within(canvasElement);
const buttons = await canvas.findAllByRole("button");
for (const button of buttons) {
const testID = button.getAttribute("data-testid");
// skip disabled buttons
if (button.hasAttribute("disabled")) {
continue;
}
await step(`Click on ${testID}`, async () => {
// check for the loader
const loaders = button.getElementsByClassName("loader");
await expect(loaders.length).toEqual(1);
// assert its width is 0 before we click
const [loader] = loaders;
await expect(loader.clientWidth).toEqual(0);
// move the mouse over the button
await userEvent.hover(button);
// the pointer should be normal
await expect(getCursorStyle(button)).toEqual("pointer");
// click the button
await userEvent.click(button);
// check the button has changed
await waitFor(async () => {
// the action handler should have been called
await expect(args.onAction).toHaveBeenCalled();
// the button should have a loading class
await expect(button).toHaveClass("loading");
// the loader should be visible
await expect(loader.clientWidth).toBeGreaterThan(0);
// the pointer should have changed to wait
await expect(getCursorStyle(button)).toEqual("wait");
});
// wait for the action handler to finish
await waitFor(async () => {
// the loading class should be removed
await expect(button).not.toHaveClass("loading");
// the loader should be hidden
await expect(loader.clientWidth).toEqual(0);
// the pointer should be normal
await expect(getCursorStyle(button)).toEqual("pointer");
});
});
}
},
};
export const Secondary: Story = {
args: {
...Primary.args,
hierarchy: "secondary",
},
play: Primary.play,
};
export const GhostPrimary: Story = {
args: {
...Primary.args,
hierarchy: "primary",
ghost: true,
},
play: Primary.play,
decorators: [
(Story) => (
<div class="bg-def-3 p-10">
<Story />
</div>
),
],
};
export const GhostSecondary: Story = {
args: {
...Primary.args,
hierarchy: "secondary",
ghost: true,
},
play: Primary.play,
};

View File

@@ -0,0 +1,116 @@
import { splitProps, type JSX, createSignal, Show } from "solid-js";
import cx from "classnames";
import { Typography } from "../Typography/Typography";
import { Button as KobalteButton } from "@kobalte/core/button";
import "./Button.css";
import Icon, { IconVariant } from "@/src/components/v2/Icon/Icon";
import { Loader } from "@/src/components/v2/Loader/Loader";
export type Size = "default" | "s";
export type Hierarchy = "primary" | "secondary";
export type Action = () => Promise<void>;
export interface ButtonProps
extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
hierarchy?: Hierarchy;
size?: Size;
ghost?: boolean;
children?: JSX.Element;
icon?: IconVariant;
startIcon?: IconVariant;
endIcon?: IconVariant;
class?: string;
onAction?: Action;
}
const iconSizes: Record<Size, string> = {
default: "1rem",
s: "0.8125rem",
};
export const Button = (props: ButtonProps) => {
const [local, other] = splitProps(props, [
"children",
"hierarchy",
"size",
"ghost",
"icon",
"startIcon",
"endIcon",
"class",
"onAction",
]);
const size = local.size || "default";
const hierarchy = local.hierarchy || "primary";
const [loading, setLoading] = createSignal(false);
const onClick = async () => {
if (!local.onAction) {
console.error("this should not be possible");
return;
}
setLoading(true);
try {
await local.onAction();
} catch (error) {
console.error("Error while executing action", error);
}
setLoading(false);
};
const iconSize = iconSizes[local.size || "default"];
return (
<KobalteButton
class={cx(
local.class,
"button", // default button class
size,
hierarchy,
{
icon: local.icon,
loading: loading(),
ghost: local.ghost,
},
)}
onClick={local.onAction ? onClick : undefined}
{...other}
>
<Loader hierarchy={hierarchy} />
{local.startIcon && (
<Icon icon={local.startIcon} class="icon-start" size={iconSize} />
)}
{local.icon && !local.children && (
<Icon icon={local.icon} class="icon" size={iconSize} />
)}
{local.children && !local.icon && (
<Typography
class="label"
hierarchy="label"
family="mono"
size={local.size || "default"}
color="inherit"
inverted={local.hierarchy === "secondary"}
weight="bold"
tag="span"
>
{local.children}
</Typography>
)}
{local.endIcon && (
<Icon icon={local.endIcon} class="icon-end" size={iconSize} />
)}
</KobalteButton>
);
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Component, For } from "solid-js";
import Icon, { IconProps, IconVariant } from "./Icon";
const iconVariants: IconVariant[] = [
"ClanIcon",
"Checkmark",
"Paperclip",
"Expand",
"Plus",
"Trash",
"Folder",
"CaretRight",
"CaretLeft",
"CaretUp",
"CaretDown",
"Close",
"Flash",
"EyeClose",
"EyeOpen",
"Settings",
"Grid",
"List",
"Edit",
"Load",
"ArrowRight",
"ArrowLeft",
"ArrowTop",
"ArrowBottom",
"Info",
"Update",
"Reload",
"Search",
"Report",
"Cursor",
"More",
"Filter",
"Download",
"Attention",
"User",
"WarningFilled",
"Modules",
"NewMachine",
"AI",
"Heart",
"SearchFilled",
"Offline",
"Switch",
"Tag",
"Machine",
];
const IconExamples: Component<IconProps> = (props) => (
<div class="inline-flex flex-wrap gap-2">
<For each={iconVariants}>{(item) => <Icon {...props} icon={item} />}</For>
</div>
);
const meta: Meta<IconProps> = {
title: "Components/Icon",
component: IconExamples,
};
export default meta;
type Story = StoryObj<IconProps>;
export const Default: Story = {};
export const Large: Story = {
args: {
width: "2rem",
height: "2rem",
},
};

View File

@@ -0,0 +1,126 @@
import cx from "classnames";
import { Component, JSX, Show, splitProps } from "solid-js";
import ArrowBottom from "@/icons/arrow-bottom.svg";
import ArrowLeft from "@/icons/arrow-left.svg";
import ArrowRight from "@/icons/arrow-right.svg";
import ArrowTop from "@/icons/arrow-top.svg";
import Attention from "@/icons/attention.svg";
import CaretDown from "@/icons/caret-down.svg";
import CaretLeft from "@/icons/caret-left.svg";
import CaretRight from "@/icons/caret-right.svg";
import CaretUp from "@/icons/caret-up.svg";
import Checkmark from "@/icons/checkmark.svg";
import ClanIcon from "@/icons/clan-icon.svg";
import Cursor from "@/icons/cursor.svg";
import Close from "@/icons/close.svg";
import Download from "@/icons/download.svg";
import Edit from "@/icons/edit.svg";
import Expand from "@/icons/expand.svg";
import EyeClose from "@/icons/eye-close.svg";
import EyeOpen from "@/icons/eye-open.svg";
import Filter from "@/icons/filter.svg";
import Flash from "@/icons/flash.svg";
import Folder from "@/icons/folder.svg";
import Grid from "@/icons/grid.svg";
import Info from "@/icons/info.svg";
import List from "@/icons/list.svg";
import Load from "@/icons/load.svg";
import More from "@/icons/more.svg";
import Paperclip from "@/icons/paperclip.svg";
import Plus from "@/icons/plus.svg";
import Reload from "@/icons/reload.svg";
import Report from "@/icons/report.svg";
import Search from "@/icons/search.svg";
import Settings from "@/icons/settings.svg";
import Trash from "@/icons/trash.svg";
import Update from "@/icons/update.svg";
import WarningFilled from "@/icons/warning-filled.svg";
import Modules from "@/icons/modules.svg";
import NewMachine from "@/icons/new-machine.svg";
import AI from "@/icons/ai.svg";
import User from "@/icons/user.svg";
import Heart from "@/icons/heart.svg";
import SearchFilled from "@/icons/search-filled.svg";
import Offline from "@/icons/offline.svg";
import Switch from "@/icons/switch.svg";
import Tag from "@/icons/tag.svg";
import Machine from "@/icons/machine.svg";
import Loader from "@/icons/loader.svg";
import { Dynamic } from "solid-js/web";
const icons = {
AI,
ArrowBottom,
ArrowLeft,
ArrowRight,
ArrowTop,
Attention,
CaretDown,
CaretLeft,
CaretRight,
CaretUp,
Checkmark,
ClanIcon,
Close,
Cursor,
Download,
Edit,
Expand,
EyeClose,
EyeOpen,
Filter,
Flash,
Folder,
Grid,
Heart,
Info,
List,
Load,
Machine,
Modules,
More,
NewMachine,
Offline,
Paperclip,
Plus,
Reload,
Report,
Search,
SearchFilled,
Settings,
Switch,
Tag,
Trash,
Update,
User,
WarningFilled,
};
export type IconVariant = keyof typeof icons;
export interface IconProps extends JSX.SvgSVGAttributes<SVGElement> {
icon: IconVariant;
class?: string;
size?: number | string;
}
const Icon: Component<IconProps> = (props) => {
const [local, iconProps] = splitProps(props, ["icon", "class"]);
const IconComponent = () => icons[local.icon];
return IconComponent() ? (
<Dynamic
component={IconComponent()}
class={cx("icon", local.class)}
width={iconProps.size || "1em"}
height={iconProps.size || "1em"}
viewBox="0 0 48 48"
// @ts-expect-error: dont know, fix this type nit later
ref={iconProps.ref}
{...iconProps}
/>
) : undefined;
};
export default Icon;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
.loader {
@apply relative;
@apply w-4 h-4;
&.primary {
& > div.wrapper > div.parent,
& > div.child {
background: #00ff57;
}
}
&.secondary {
& > div.wrapper > div.parent,
& > div.child {
background: #0051ff;
}
}
& > div.wrapper {
@apply absolute top-0 left-0 w-full h-full;
transform: translate(0%, 0%) rotate(-45deg);
animation: moveLoaderWrapper 1.8s ease-in-out infinite;
& > div.parent {
@apply absolute top-1/2 left-1/2;
@apply w-2/3 h-2/3;
border-radius: 50%;
animation: moveLoaderParent 1.8s ease-in-out infinite;
transform: translateX(-50%) translateY(-50%);
}
}
& > .child {
@apply absolute z-10 top-1/2 left-1/2 w-1/2 h-1/2;
border-radius: 50%;
transform: translate(-50%, -50%);
animation: moveLoaderChild 1.8s ease-in-out infinite;
}
}
@keyframes moveLoaderWrapper {
0% {
transform: translate(0%, 0%) rotate(-45deg);
}
35% {
transform: translate(-25%, 0%) rotate(-45deg);
}
50% {
transform: translate(0%, 0%) rotate(-45deg);
}
85% {
transform: translate(25%, 0%) rotate(-45deg);
}
}
@keyframes moveLoaderParent {
0% {
animation-timing-function: ease-in-out;
transform: translateX(-50%) translateY(-50%);
}
35% {
animation-timing-function: cubic-bezier(0.7, -0.9, 0.3, 3.2);
transform: translateX(-50%) translateY(-50%) skew(20deg, 20deg);
}
50% {
animation-timing-function: ease-in-out;
transform: translateX(-50%) translateY(-50%) skew(0deg, 0deg);
}
85% {
animation-timing-function: cubic-bezier(0.7, -0.9, 0.3, 3.2);
transform: translateX(-50%) translateY(-50%) skew(20deg, 20deg);
}
}
@keyframes moveLoaderChild {
0% {
animation-timing-function: ease-in-out;
transform: translateX(-50%) translateY(-50%);
}
35% {
animation-timing-function: cubic-bezier(0.7, -0.9, 0.3, 3.2);
transform: translateX(50%) translateY(-50%) scale(0.56);
}
50% {
animation-timing-function: ease-in-out;
transform: translateX(-50%) translateY(-50%);
}
85% {
animation-timing-function: cubic-bezier(0.7, -0.9, 0.3, 3.2);
transform: translateX(-150%) translateY(-50%) scale(0.56);
}
}

View File

@@ -0,0 +1,23 @@
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Loader, LoaderProps } from "@/src/components/v2/Loader/Loader";
const meta: Meta<LoaderProps> = {
title: "Components/Loader",
component: Loader,
};
export default meta;
type Story = StoryObj<LoaderProps>;
export const Primary: Story = {
args: {
hierarchy: "primary",
},
};
export const Secondary: Story = {
args: {
hierarchy: "secondary",
},
};

View File

@@ -0,0 +1,19 @@
import "./Loader.css";
import cx from "classnames";
export type Hierarchy = "primary" | "secondary";
export interface LoaderProps {
hierarchy?: Hierarchy;
}
export const Loader = (props: LoaderProps) => {
return (
<div class={cx("loader", props.hierarchy || "primary")}>
<div class="wrapper">
<div class="parent"></div>
</div>
<div class="child"></div>
</div>
);
};

View File

@@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Components/Loader Primary smoke-test 1`] = `
<div class="loader primary">
<div class="wrapper">
<div class="parent">
</div>
</div>
<div class="child">
</div>
</div>
`;
exports[`Components/Loader Secondary smoke-test 1`] = `
<div class="loader secondary">
<div class="wrapper">
<div class="parent">
</div>
</div>
<div class="child">
</div>
</div>
`;

View File

@@ -23,25 +23,25 @@
&.font-size-default {
font-size: 1rem;
line-height: 132%;
line-height: 1.32;
letter-spacing: 0.02rem;
}
&.font-size-s {
font-size: 0.875rem;
line-height: 132%;
line-height: 1.32;
letter-spacing: 0.0175rem;
}
&.font-size-xs {
font-size: 0.75rem;
line-height: 132%;
line-height: 1.32;
letter-spacing: 0.0225rem;
}
&.font-size-xxs {
font-size: 0.6875rem;
line-height: 132%;
line-height: 1.32;
letter-spacing: 0.00688rem;
}
}
@@ -52,38 +52,41 @@
&.font-size-default {
font-size: 0.875rem;
line-height: 132%;
line-height: 1.32;
letter-spacing: 0.0175rem;
}
&.font-size-s {
font-size: 0.8125rem;
line-height: 132%;
line-height: 1.32;
letter-spacing: 0.0175rem;
}
&.font-size-xs {
font-size: 0.75rem;
line-height: 132%;
line-height: 1.32;
letter-spacing: 0.0075rem;
}
}
&.font-family-mono {
font-family: "Fira Code", monospace;
font-family: "Commit Mono", monospace;
&.font-size-default {
font-size: 0.8125rem;
line-height: 0;
letter-spacing: normal;
}
&.font-size-s {
font-size: 0.75rem;
line-height: 0;
letter-spacing: normal;
}
&.font-size-xs {
font-size: 0.6875rem;
line-height: 0;
letter-spacing: normal;
}
}

View File

@@ -90,26 +90,6 @@ const sizeHierarchyMap: SizeForHierarchy = {
},
};
interface FamilyForHierarchy {
body: {
regular: string;
condensed: string;
};
label: {
condensed: string;
mono: string;
};
title: {
condensed: string;
};
headline: {
regular: string;
};
teaser: {
regular: string;
};
}
const defaultFamilyMap: Record<Hierarchy, Family> = {
body: "condensed",
label: "condensed",
@@ -144,7 +124,7 @@ export const Typography = <H extends Hierarchy>(props: _TypographyProps<H>) => {
const color = () => colorFor(props.color, props.inverted);
const classList = mergeProps(props.classList, {
"font-body": props.hierarchy === "body" || !!props.hierarchy,
"font-body": props.hierarchy === "body" || !props.hierarchy,
"font-label": props.hierarchy === "label",
"font-title": props.hierarchy === "title",
"font-headline": props.hierarchy === "headline",

View File

@@ -713,51 +713,51 @@ exports[`Components/Typography Headline smoke-test 1`] = `
<tbody>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-default font-body font-headline">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-default font-headline">
headline / default / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-default font-body font-headline">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-default font-headline">
headline / default / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-default font-body font-headline">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-default font-headline">
headline / default / bold
</span>
</td>
</tr>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-m font-body font-headline">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-m font-headline">
headline / m / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-m font-body font-headline">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-m font-headline">
headline / m / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-m font-body font-headline">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-m font-headline">
headline / m / bold
</span>
</td>
</tr>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-l font-body font-headline">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-l font-headline">
headline / l / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-l font-body font-headline">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-l font-headline">
headline / l / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-l font-body font-headline">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-l font-headline">
headline / l / bold
</span>
</td>
@@ -771,51 +771,51 @@ exports[`Components/Typography LabelCondensed smoke-test 1`] = `
<tbody>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-condensed font-weight-normal font-size-default font-body font-label">
<span class="typography fg-def-1 font-family-condensed font-weight-normal font-size-default font-label">
label / default / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-condensed font-weight-medium font-size-default font-body font-label">
<span class="typography fg-def-1 font-family-condensed font-weight-medium font-size-default font-label">
label / default / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-condensed font-weight-bold font-size-default font-body font-label">
<span class="typography fg-def-1 font-family-condensed font-weight-bold font-size-default font-label">
label / default / bold
</span>
</td>
</tr>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-condensed font-weight-normal font-size-s font-body font-label">
<span class="typography fg-def-1 font-family-condensed font-weight-normal font-size-s font-label">
label / s / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-condensed font-weight-medium font-size-s font-body font-label">
<span class="typography fg-def-1 font-family-condensed font-weight-medium font-size-s font-label">
label / s / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-condensed font-weight-bold font-size-s font-body font-label">
<span class="typography fg-def-1 font-family-condensed font-weight-bold font-size-s font-label">
label / s / bold
</span>
</td>
</tr>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-condensed font-weight-normal font-size-xs font-body font-label">
<span class="typography fg-def-1 font-family-condensed font-weight-normal font-size-xs font-label">
label / xs / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-condensed font-weight-medium font-size-xs font-body font-label">
<span class="typography fg-def-1 font-family-condensed font-weight-medium font-size-xs font-label">
label / xs / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-condensed font-weight-bold font-size-xs font-body font-label">
<span class="typography fg-def-1 font-family-condensed font-weight-bold font-size-xs font-label">
label / xs / bold
</span>
</td>
@@ -829,51 +829,51 @@ exports[`Components/Typography LabelMono smoke-test 1`] = `
<tbody>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-mono font-weight-normal font-size-default font-body font-label">
<span class="typography fg-def-1 font-family-mono font-weight-normal font-size-default font-label">
label / default / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-mono font-weight-medium font-size-default font-body font-label">
<span class="typography fg-def-1 font-family-mono font-weight-medium font-size-default font-label">
label / default / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-mono font-weight-bold font-size-default font-body font-label">
<span class="typography fg-def-1 font-family-mono font-weight-bold font-size-default font-label">
label / default / bold
</span>
</td>
</tr>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-mono font-weight-normal font-size-s font-body font-label">
<span class="typography fg-def-1 font-family-mono font-weight-normal font-size-s font-label">
label / s / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-mono font-weight-medium font-size-s font-body font-label">
<span class="typography fg-def-1 font-family-mono font-weight-medium font-size-s font-label">
label / s / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-mono font-weight-bold font-size-s font-body font-label">
<span class="typography fg-def-1 font-family-mono font-weight-bold font-size-s font-label">
label / s / bold
</span>
</td>
</tr>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-mono font-weight-normal font-size-xs font-body font-label">
<span class="typography fg-def-1 font-family-mono font-weight-normal font-size-xs font-label">
label / xs / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-mono font-weight-medium font-size-xs font-body font-label">
<span class="typography fg-def-1 font-family-mono font-weight-medium font-size-xs font-label">
label / xs / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-mono font-weight-bold font-size-xs font-body font-label">
<span class="typography fg-def-1 font-family-mono font-weight-bold font-size-xs font-label">
label / xs / bold
</span>
</td>
@@ -887,7 +887,7 @@ exports[`Components/Typography Teaser smoke-test 1`] = `
<tbody>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-default font-body font-teaser">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-default font-teaser">
teaser / default / bold
</span>
</td>
@@ -901,51 +901,51 @@ exports[`Components/Typography Title smoke-test 1`] = `
<tbody>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-default font-body font-title">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-default font-title">
title / default / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-default font-body font-title">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-default font-title">
title / default / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-default font-body font-title">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-default font-title">
title / default / bold
</span>
</td>
</tr>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-m font-body font-title">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-m font-title">
title / m / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-m font-body font-title">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-m font-title">
title / m / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-m font-body font-title">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-m font-title">
title / m / bold
</span>
</td>
</tr>
<tr class="border-b border-def-3 even:bg-def-2">
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-l font-body font-title">
<span class="typography fg-def-1 font-family-regular font-weight-normal font-size-l font-title">
title / l / normal
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-l font-body font-title">
<span class="typography fg-def-1 font-family-regular font-weight-medium font-size-l font-title">
title / l / medium
</span>
</td>
<td class="px-6 py-2 ">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-l font-body font-title">
<span class="typography fg-def-1 font-family-regular font-weight-bold font-size-l font-title">
title / l / bold
</span>
</td>

View File

@@ -43,9 +43,50 @@
}
@font-face {
font-family: "Fira Code";
font-family: "Commit Mono";
font-weight: 400;
src: url(../../../.fonts/FiraCode-Regular.woff2) format("woff2");
src: url(../../../.fonts/CommitMono-400-Regular.otf) format("otf");
}
:root {
--clr-bg-def-1: theme(colors.white);
--clr-bg-def-2: theme(colors.primary.50);
--clr-bg-def-3: theme(colors.secondary.100);
--clr-bg-def-4: theme(colors.secondary.200);
--clr-border-def-1: theme(colors.secondary.50);
--clr-border-def-2: theme(colors.secondary.100);
--clr-border-def-3: theme(colors.secondary.200);
--clr-border-def-4: theme(colors.secondary.300);
--clr-border-def-sem-inf-1: theme(colors.info.500);
--clr-border-def-sem-inf-2: theme(colors.info.600);
--clr-border-def-sem-inf-3: theme(colors.info.700);
--clr-border-def-sem-inf-4: theme(colors.info.800);
--clr-bg-inv-1: theme(colors.primary.600);
--clr-bg-inv-2: theme(colors.primary.700);
--clr-bg-inv-3: theme(colors.primary.800);
--clr-bg-inv-4: theme(colors.primary.900);
--clr-border-inv-1: theme(colors.secondary.700);
--clr-border-inv-2: theme(colors.secondary.800);
--clr-border-inv-3: theme(colors.secondary.900);
--clr-border-inv-4: theme(colors.secondary.950);
--clr-bg-inv-acc-1: theme(colors.secondary.500);
--clr-bg-inv-acc-2: theme(colors.secondary.600);
--clr-bg-inv-acc-3: theme(colors.secondary.700);
--clr-fg-def-1: theme(colors.secondary.950);
--clr-fg-def-2: theme(colors.secondary.900);
--clr-fg-def-3: theme(colors.secondary.700);
--clr-fg-def-4: theme(colors.secondary.400);
--clr-fg-inv-1: theme(colors.white);
--clr-fg-inv-2: theme(colors.secondary.100);
--clr-fg-inv-3: theme(colors.secondary.300);
--clr-fg-inv-4: theme(colors.secondary.400);
}
html {