Skip to content

Commit

Permalink
migrate color
Browse files Browse the repository at this point in the history
  • Loading branch information
NgocNhi123 committed Jun 30, 2024
1 parent 2b24b71 commit f2a6f7a
Show file tree
Hide file tree
Showing 28 changed files with 761 additions and 7 deletions.
Binary file modified .yarn/install-state.gz
Binary file not shown.
6 changes: 5 additions & 1 deletion new-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
"_build": "storybook build"
},
"dependencies": {
"color": "^4.2.3",
"formik": "^2.4.6",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.0"
},
"devDependencies": {
"@chromatic-com/storybook": "^1.6.0",
Expand All @@ -21,6 +24,7 @@
"@storybook/react": "^8.1.11",
"@storybook/react-vite": "^8.1.11",
"@storybook/test": "^8.1.11",
"@types/color": "^3.0.6",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.14.1",
Expand Down
37 changes: 37 additions & 0 deletions new-docs/src/color/background/background.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Canvas, Meta, Story } from "@storybook/blocks";
import { ColorBackground } from "./background";

<Meta title="Patterns/Color/Background" />

# Background Color

The `background` utility contains classes that set the
[CSS `background-color`][1] property. This is the recommended way to set
background color since they automatically change based on the current theme.

```ts
import { background } from "@moai/core";

<div className={background.weak}>Text</div>;
```

There are only 2 colors in the `background` utility at the moment:

<ColorBackground rows={[{ key: "weak" }, { key: "strong" }]} />

The `weak` value sets a light gray background on light theme and a daker
background on dark theme. It should be used for underlying backgrounds, like
the background of your app. It can also be used to separate an area, such as
the header of a table.

The `strong` value sets a white background on light theme and a lighter
background on dark theme. It should be used for elevated containers, such as
panes, toolbars or popovers.

## See also

- The [Pane][2] component uses `strong` background along with border and shadow
to better elevate contents.

[1]: https://developer.mozilla.org/en-US/docs/Web/CSS/background-color
[2]: /docs/components-pane-docs
11 changes: 11 additions & 0 deletions new-docs/src/color/background/background.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.name {
font-family: "Source Code Pro", monospace;
width: 160px;
min-width: 160px;
}

.container {
overflow: auto;
white-space: nowrap;
border-width: 2px;
}
48 changes: 48 additions & 0 deletions new-docs/src/color/background/background.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { border, background, Table, text } from "../../../../core/src";
import { ColorSample } from "../sample/sample";
import s from "./background.module.css";

type BackgroundKey = keyof typeof background;

interface Row {
key: BackgroundKey;
}

const MakeColumn =
(theme: "light" | "dark", text: string) =>
(row: Row): JSX.Element => (
<div className={theme}>
<ColorSample
background={background[row.key]}
foreground={{ type: "text", cls: text, usage: "both" }}
/>
</div>
);

const LightStrong = MakeColumn("light", text.normal);
const LightWeak = MakeColumn("light", text.muted);
const DarkStrong = MakeColumn("dark", text.normal);
const DarkWeak = MakeColumn("dark", text.muted);

interface Props {
rows: Row[];
}

export const ColorBackground = (props: Props): JSX.Element => (
<div className={[s.container, border.weak].join(" ")}>
<Table<Row>
size={Table.sizes.small}
fixed={{ firstColumn: true }}
fill
rows={props.rows}
rowKey={(row) => row.key}
columns={[
{ title: "Name", className: s.name, render: "key" },
{ title: "Light", render: LightStrong },
{ title: "Light (muted)", render: LightWeak },
{ title: "Dark", render: DarkStrong },
{ title: "Dark (muted)", render: DarkWeak },
]}
/>
</div>
);
28 changes: 28 additions & 0 deletions new-docs/src/color/border/border.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Meta, Canvas, Story } from "@storybook/blocks";
import { ColorBorder } from "./border";

<Meta title="Patterns/Color/Border" />

# Border Color

The `border` utility contains classes that set the [CSS `border-color`][1]
property. This is the recommended way to set border color since they
automatically change based on the current theme.

Note that this only set the color. To have a border, you also need to set a
width, e.g. using the "border" class from Tailwind. You don't need to set the
border style since it's already default to "solid" in our [CSS reset][2].

```ts
import { border } from "@moai/core";

// The "border" class comes from Tailwind to set the border width
<div className={[border.weak, "border"].join(" ")}>Text</div>;
```

There are 2 colors in the `border` utility at the moment:

<ColorBorder rows={[{ key: "weak" }, { key: "strong" }]} />

[1]: https://developer.mozilla.org/en-US/docs/Web/CSS/border-color
[2]: https://github.com/moaijs/moai/blob/739a87de82bd061bb41f38c5a51a410b59944a3d/lib/core/src/style/reset.css#L404-L417
11 changes: 11 additions & 0 deletions new-docs/src/color/border/border.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.name {
font-family: "Source Code Pro", monospace;
width: 160px;
min-width: 160px;
}

.container {
overflow: auto;
white-space: nowrap;
border-width: 2px;
}
48 changes: 48 additions & 0 deletions new-docs/src/color/border/border.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { background, border, Table } from "../../../../core/src";
import { ColorSample } from "../sample/sample";
import s from "./border.module.css";

type BorderKey = keyof typeof border;

interface Row {
key: BorderKey;
}

const MakeColumn =
(theme: "light" | "dark", back: string) =>
(row: Row): JSX.Element => (
<div className={theme}>
<ColorSample
background={back}
foreground={{ type: "border", cls: border[row.key] }}
/>
</div>
);

const LightStrong = MakeColumn("light", background.strong);
const LightWeak = MakeColumn("light", background.weak);
const DarkStrong = MakeColumn("dark", background.strong);
const DarkWeak = MakeColumn("dark", background.weak);

interface Props {
rows: Row[];
}

export const ColorBorder = (props: Props): JSX.Element => (
<div className={s.container}>
<Table<Row>
size={Table.sizes.small}
fixed={{ firstColumn: true }}
fill
rows={props.rows}
rowKey={(row) => row.key}
columns={[
{ title: "Name", className: s.name, render: "key" },
{ title: "Light", render: LightStrong },
{ title: "Light (alt bg)", render: LightWeak },
{ title: "Dark", render: DarkStrong },
{ title: "Dark (alt bg)", render: DarkWeak },
]}
/>
</div>
);
23 changes: 23 additions & 0 deletions new-docs/src/color/category/category.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Meta, Canvas, Story } from "@storybook/blocks";
import { ColorCategoryTable } from "./category";

<Meta title="Patterns/Color/Category" />

# Category Colors

Category colors have no semantic meaning attached. Instead, they are used to
show relationships between elements (e.g. categorization, labelling in data
visualizations). They still conform to the AA level of [WCAG of contrast
ratios][1].

## Usage

[At the moment][2], Moai does not support using category colors directly.
Instead, category colors are used via several components that support them:

<ColorCategoryTable />

(The list is quite short at the moment. We are adding more to it!)

[1]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Perceivable/Color_contrast
[2]: https://github.com/moaijs/moai/issues/210
3 changes: 3 additions & 0 deletions new-docs/src/color/category/category.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.container {
border-width: 2px;
}
37 changes: 37 additions & 0 deletions new-docs/src/color/category/category.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";
import { border, Table } from "../../../../core/src";
import { GalleryTag } from "../../../../gallery/src";
import s from "./category.module.css";

interface Row {
name: string;
link: string;
example: React.ReactNode;
}

const Name = (row: Row): JSX.Element => (
<a href={row.link} children={row.name} />
);

const Example = (row: Row): JSX.Element => <div children={row.example} />;

export const ColorCategoryTable = (): JSX.Element => (
<div className={[s.container, border.weak].join(" ")}>
<Table<Row>
rows={[
{
name: "Tag",
link: "/docs/components-tag",
example: <GalleryTag />,
},
]}
rowKey={(row) => row.name}
columns={[
{ title: "Component", render: Name },
{ title: "Example", render: Example },
]}
fill
fixed={{ firstColumn: true }}
/>
</div>
);
20 changes: 20 additions & 0 deletions new-docs/src/color/color.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Canvas, Meta, Story } from "@storybook/blocks";

<Meta title="Patterns/Color" />

# Colors

For recommended color usages, see [Text][1], [Background][2], and [Border][3].
They provide accessible colors that are automatically changed based on the
current theme.

For colors that are persistent across themes, see [Static Colors][4].

For colors that have no semantic meaning and should be used for data
visualizations, see [Category Colors][5]

[1]: /docs/patterns-color-text--docs
[2]: /docs/patterns-color-background--docs
[3]: /docs/patterns-color-border--docs
[4]: /docs/patterns-color-static--docs
[5]: /docs/patterns-color-category--docs
17 changes: 17 additions & 0 deletions new-docs/src/color/sample/sample.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.container {
padding: 12px;
width: 120px;
font-variant-numeric: "tabular-nums";
border-radius: 4px;

display: flex;
justify-content: space-between;
align-items: center;
}

.border {
width: 16px;
height: 16px;
border-radius: 16px;
border-width: 1px;
}
82 changes: 82 additions & 0 deletions new-docs/src/color/sample/sample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Color from "color";
import { useEffect, useRef, useState } from "react";
import { HiCheckCircle } from "react-icons/hi";
import { CategoryColor, categoryColors, Icon, Tag } from "../../../../core/src";
import s from "./sample.module.css";

export type ColorSampleUsage = "text" | "icon" | "both";

interface Props {
background: string;
foreground:
| { type: "text"; cls: string; usage: ColorSampleUsage }
| { type: "border"; cls: string };
}

const getColor = (contrast: number): CategoryColor => {
const rounded = Math.round(contrast * 10) / 10;
if (rounded >= 4.5) return categoryColors.green;
if (rounded >= 3) return categoryColors.yellow;
return categoryColors.red;
};

const getContrast = (
props: Props,
backElement: HTMLDivElement,
foreElement: HTMLElement,
): number => {
const back = window.getComputedStyle(backElement).backgroundColor;
const foreStyle = window.getComputedStyle(foreElement);
const isText = props.foreground.type === "text";
// Only use long hand name.
// See: https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle#notes
const fore = foreStyle[isText ? "color" : "borderLeftColor"];
return Color(back).contrast(Color(fore));
};

const ColorIcon = (): JSX.Element => (
<Icon component={HiCheckCircle} size={16} display="inline" />
);

export const ColorSample = (props: Props): JSX.Element => {
const backRef = useRef<HTMLDivElement>(null);
const foreRef = useRef<HTMLElement>(null);
const [contrast, setContrast] = useState(0);

useEffect(() => {
window.setTimeout(() => {
const [back, fore] = [backRef.current, foreRef.current];
if (back === null) throw Error("backElm is null");
if (fore === null) throw Error("foreElm is null");
setContrast(getContrast(props, back, fore));
}, 0); // Wait for all styles are applied
}, [setContrast]);

Check warning on line 53 in new-docs/src/color/sample/sample.tsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has a missing dependency: 'props'. Either include it or remove the dependency array. If 'setContrast' needs the current value of 'props', you can also switch to useReducer instead of useState and read 'props' in the reducer

return (
<div
ref={backRef}
className={[props.background, s.container].join(" ")}
>
{/* "background" also set color so the "fore" must be in another
element of the "back" */}
{props.foreground.type === "text" ? (
<span ref={foreRef} className={props.foreground.cls}>
{props.foreground.usage !== "icon" && <span>Aa</span>}
{props.foreground.usage === "both" && <span> </span>}
{props.foreground.usage !== "text" && <ColorIcon />}
</span>
) : (
<span
ref={foreRef}
className={[s.border, props.foreground.cls].join(" ")}
/>
)}
<span title="Color contrast" aria-label="Color contrast">
<Tag
color={getColor(contrast)}
children={contrast.toFixed(1)}
/>
</span>
</div>
);
};
Loading

0 comments on commit f2a6f7a

Please sign in to comment.