diff --git a/apps/wow-docs/app/page.tsx b/apps/wow-docs/app/page.tsx
index d89460d2..a3e944cb 100644
--- a/apps/wow-docs/app/page.tsx
+++ b/apps/wow-docs/app/page.tsx
@@ -10,6 +10,10 @@ import RadioButton from "wowds-ui/RadioButton";
import RadioGroup from "wowds-ui/RadioGroup";
import SearchBar from "wowds-ui/SearchBar";
import Switch from "wowds-ui/Switch";
+import Tabs from "wowds-ui/Tabs";
+import TabsContent from "wowds-ui/TabsContent";
+import TabsItem from "wowds-ui/TabsItem";
+import TabsList from "wowds-ui/TabsList";
const Home = () => {
return (
@@ -43,6 +47,22 @@ const Home = () => {
+
+
+ 첫번째첫번째첫번째첫번째
+ 두 번째
+ 세 번쨰
+
+
+ 첫번째 탭
+
+
+ 두번째 탭
+
+
+ 세번째 탭
+
+
>
);
};
diff --git a/apps/wow-docs/styled-system/tokens/index.js b/apps/wow-docs/styled-system/tokens/index.js
index 930662c7..a6ec6779 100644
--- a/apps/wow-docs/styled-system/tokens/index.js
+++ b/apps/wow-docs/styled-system/tokens/index.js
@@ -343,6 +343,10 @@ const tokens = {
value: 10,
variable: "var(--z-index-dropdown)",
},
+ "zIndex.overlay": {
+ value: 9999,
+ variable: "var(--z-index-overlay)",
+ },
"shadows.blue": {
value: "0px 4px 8px 0px rgba(16, 43, 74, 0.2)",
variable: "var(--shadows-blue)",
diff --git a/apps/wow-docs/styled-system/tokens/tokens.d.ts b/apps/wow-docs/styled-system/tokens/tokens.d.ts
index 24b9c593..45319136 100644
--- a/apps/wow-docs/styled-system/tokens/tokens.d.ts
+++ b/apps/wow-docs/styled-system/tokens/tokens.d.ts
@@ -86,6 +86,7 @@ export type Token =
| "borderWidths.button"
| "borderWidths.arrow"
| "zIndex.dropdown"
+ | "zIndex.overlay"
| "shadows.blue"
| "shadows.mono"
| "breakpoints.xs"
@@ -323,7 +324,7 @@ export type RadiusToken = "sm" | "md" | "full";
export type BorderWidthToken = "button" | "arrow";
-export type ZIndexToken = "dropdown";
+export type ZIndexToken = "dropdown" | "overlay";
export type ShadowToken = "blue" | "mono";
diff --git a/apps/wow-docs/styled-system/types/prop-type.d.ts b/apps/wow-docs/styled-system/types/prop-type.d.ts
index 195159ce..bfcf5a77 100644
--- a/apps/wow-docs/styled-system/types/prop-type.d.ts
+++ b/apps/wow-docs/styled-system/types/prop-type.d.ts
@@ -679,7 +679,9 @@ export interface UtilityValues {
| "body3"
| "label1"
| "label2"
- | "label3";
+ | "label3"
+ | "header1"
+ | "header2";
}
type WithColorOpacityModifier = T extends string ? `${T}/${string}` : T;
diff --git a/packages/scripts/generateBuildConfig.ts b/packages/scripts/generateBuildConfig.ts
index c9bed9e1..613353d5 100644
--- a/packages/scripts/generateBuildConfig.ts
+++ b/packages/scripts/generateBuildConfig.ts
@@ -26,17 +26,22 @@ const excludedComponents = [
"CollectionContext",
"DropDownOptionList",
"pickerComponents",
+ "ToastContext",
];
+// 추가할 컴포넌트 목록
+const includedComponents = ["useToast"];
+
const getFilteredComponentFiles = async (directoryPath: string) => {
const files = await fs.readdir(directoryPath, { recursive: true });
return files.filter(
(file) =>
- file.endsWith(".tsx") &&
- !file.includes("test") &&
- !file.includes("stories") &&
- !excludedComponents.some((excluded) => file.includes(excluded))
+ (file.endsWith(".tsx") &&
+ !file.includes("test") &&
+ !file.includes("stories") &&
+ !excludedComponents.some((excluded) => file.includes(excluded))) ||
+ includedComponents.some((included) => file.includes(included))
);
};
diff --git a/packages/scripts/generateReactComponentFromSvg.ts b/packages/scripts/generateReactComponentFromSvg.ts
index a08f184c..ad7ec5db 100644
--- a/packages/scripts/generateReactComponentFromSvg.ts
+++ b/packages/scripts/generateReactComponentFromSvg.ts
@@ -138,7 +138,9 @@ const generateExportFile = async (components: string[]) => {
)
.join("\n");
- await fs.writeFile(EXPORT_FILE_PATH, exportFileContent);
+ const resolvedExportFileContent = `export * from "../types/Icon.ts";\n${exportFileContent}`;
+
+ await fs.writeFile(EXPORT_FILE_PATH, resolvedExportFileContent);
};
(async () => {
diff --git a/packages/wow-icons/CHANGELOG.md b/packages/wow-icons/CHANGELOG.md
index cecf3299..4885acd9 100644
--- a/packages/wow-icons/CHANGELOG.md
+++ b/packages/wow-icons/CHANGELOG.md
@@ -1,5 +1,13 @@
# wowds-icons
+## 0.1.5
+
+### Patch Changes
+
+- 3682ddd: Avatar 컴포넌트를 추가합니다.
+- 185475a: Icon 공통 타입을 내보내기합니다.
+- b7f51d2: Header 컴포넌트를 추가합니다.
+
## 0.1.4
### Patch Changes
diff --git a/packages/wow-icons/package.json b/packages/wow-icons/package.json
index 447ce8c2..5eead9b1 100644
--- a/packages/wow-icons/package.json
+++ b/packages/wow-icons/package.json
@@ -1,6 +1,6 @@
{
"name": "wowds-icons",
- "version": "0.1.4",
+ "version": "0.1.5",
"description": "",
"repository": {
"type": "git",
@@ -16,7 +16,6 @@
"package.json"
],
"type": "module",
- "types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/component/index.d.ts",
diff --git a/packages/wow-icons/src/component/BlueAvatar.tsx b/packages/wow-icons/src/component/BlueAvatar.tsx
new file mode 100644
index 00000000..6aee0b4d
--- /dev/null
+++ b/packages/wow-icons/src/component/BlueAvatar.tsx
@@ -0,0 +1,83 @@
+import { forwardRef } from "react";
+
+import type { IconProps } from "@/types/Icon.ts";
+
+const BlueAvatar = forwardRef(
+ (
+ {
+ className,
+ width = "100",
+ height = "100",
+ viewBox = "0 0 100 100",
+ ...rest
+ },
+ ref
+ ) => {
+ return (
+
+ );
+ }
+);
+
+BlueAvatar.displayName = "BlueAvatar";
+export default BlueAvatar;
diff --git a/packages/wow-icons/src/component/GdscLogo.tsx b/packages/wow-icons/src/component/GdscLogo.tsx
new file mode 100644
index 00000000..cba980ef
--- /dev/null
+++ b/packages/wow-icons/src/component/GdscLogo.tsx
@@ -0,0 +1,51 @@
+import { forwardRef } from "react";
+
+import type { IconProps } from "@/types/Icon.ts";
+
+const GdscLogo = forwardRef(
+ (
+ { className, width = "49", height = "24", viewBox = "0 0 49 24", ...rest },
+ ref
+ ) => {
+ return (
+
+ );
+ }
+);
+
+GdscLogo.displayName = "GdscLogo";
+export default GdscLogo;
diff --git a/packages/wow-icons/src/component/GreenAvatar.tsx b/packages/wow-icons/src/component/GreenAvatar.tsx
new file mode 100644
index 00000000..b3a465b5
--- /dev/null
+++ b/packages/wow-icons/src/component/GreenAvatar.tsx
@@ -0,0 +1,87 @@
+import { forwardRef } from "react";
+
+import type { IconProps } from "@/types/Icon.ts";
+
+const GreenAvatar = forwardRef(
+ (
+ {
+ className,
+ width = "100",
+ height = "100",
+ viewBox = "0 0 100 100",
+ ...rest
+ },
+ ref
+ ) => {
+ return (
+
+ );
+ }
+);
+
+GreenAvatar.displayName = "GreenAvatar";
+export default GreenAvatar;
diff --git a/packages/wow-icons/src/component/RedAvatar.tsx b/packages/wow-icons/src/component/RedAvatar.tsx
new file mode 100644
index 00000000..542a5ffd
--- /dev/null
+++ b/packages/wow-icons/src/component/RedAvatar.tsx
@@ -0,0 +1,84 @@
+import { forwardRef } from "react";
+
+import type { IconProps } from "@/types/Icon.ts";
+
+const RedAvatar = forwardRef(
+ (
+ {
+ className,
+ width = "100",
+ height = "100",
+ viewBox = "0 0 100 100",
+ ...rest
+ },
+ ref
+ ) => {
+ return (
+
+ );
+ }
+);
+
+RedAvatar.displayName = "RedAvatar";
+export default RedAvatar;
diff --git a/packages/wow-icons/src/component/YellowAvatar.tsx b/packages/wow-icons/src/component/YellowAvatar.tsx
new file mode 100644
index 00000000..606be0bb
--- /dev/null
+++ b/packages/wow-icons/src/component/YellowAvatar.tsx
@@ -0,0 +1,91 @@
+import { forwardRef } from "react";
+
+import type { IconProps } from "@/types/Icon.ts";
+
+const YellowAvatar = forwardRef(
+ (
+ {
+ className,
+ width = "100",
+ height = "100",
+ viewBox = "0 0 100 100",
+ ...rest
+ },
+ ref
+ ) => {
+ return (
+
+ );
+ }
+);
+
+YellowAvatar.displayName = "YellowAvatar";
+export default YellowAvatar;
diff --git a/packages/wow-icons/src/component/index.ts b/packages/wow-icons/src/component/index.ts
index 11ab8e7f..238767d8 100644
--- a/packages/wow-icons/src/component/index.ts
+++ b/packages/wow-icons/src/component/index.ts
@@ -1,14 +1,20 @@
+export * from "../types/Icon.ts";
+export { default as BlueAvatar } from "./BlueAvatar.tsx";
export { default as Calendar } from "./Calendar.tsx";
export { default as Check } from "./Check.tsx";
export { default as Close } from "./Close.tsx";
export { default as DownArrow } from "./DownArrow.tsx";
export { default as Edit } from "./Edit.tsx";
+export { default as GdscLogo } from "./GdscLogo.tsx";
+export { default as GreenAvatar } from "./GreenAvatar.tsx";
export { default as Help } from "./Help.tsx";
export { default as LeftArrow } from "./LeftArrow.tsx";
export { default as Link } from "./Link.tsx";
export { default as Plus } from "./Plus.tsx";
+export { default as RedAvatar } from "./RedAvatar.tsx";
export { default as Reload } from "./Reload.tsx";
export { default as RightArrow } from "./RightArrow.tsx";
export { default as Search } from "./Search.tsx";
export { default as Trash } from "./Trash.tsx";
export { default as Warn } from "./Warn.tsx";
+export { default as YellowAvatar } from "./YellowAvatar.tsx";
diff --git a/packages/wow-icons/src/svg/blue-avatar.svg b/packages/wow-icons/src/svg/blue-avatar.svg
new file mode 100644
index 00000000..49658c71
--- /dev/null
+++ b/packages/wow-icons/src/svg/blue-avatar.svg
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/packages/wow-icons/src/svg/gdsc-logo.svg b/packages/wow-icons/src/svg/gdsc-logo.svg
new file mode 100644
index 00000000..6cc817bb
--- /dev/null
+++ b/packages/wow-icons/src/svg/gdsc-logo.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/packages/wow-icons/src/svg/green-avatar.svg b/packages/wow-icons/src/svg/green-avatar.svg
new file mode 100644
index 00000000..5e0cfaf4
--- /dev/null
+++ b/packages/wow-icons/src/svg/green-avatar.svg
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/packages/wow-icons/src/svg/red-avatar.svg b/packages/wow-icons/src/svg/red-avatar.svg
new file mode 100644
index 00000000..26bf9bb3
--- /dev/null
+++ b/packages/wow-icons/src/svg/red-avatar.svg
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/packages/wow-icons/src/svg/yellow-avatar.svg b/packages/wow-icons/src/svg/yellow-avatar.svg
new file mode 100644
index 00000000..0dd1a239
--- /dev/null
+++ b/packages/wow-icons/src/svg/yellow-avatar.svg
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/packages/wow-theme/CHANGELOG.md b/packages/wow-theme/CHANGELOG.md
index 8a2f24f6..731f3003 100644
--- a/packages/wow-theme/CHANGELOG.md
+++ b/packages/wow-theme/CHANGELOG.md
@@ -1,5 +1,12 @@
# wowds-theme
+## 0.1.4
+
+### Patch Changes
+
+- 185475a: zIndex 토큰을 추가합니다.
+- b7f51d2: Header 컴포넌트를 추가합니다.
+
## 0.1.3
### Patch Changes
diff --git a/packages/wow-theme/package.json b/packages/wow-theme/package.json
index 1a7fe353..72f23b8e 100644
--- a/packages/wow-theme/package.json
+++ b/packages/wow-theme/package.json
@@ -1,6 +1,6 @@
{
"name": "wowds-theme",
- "version": "0.1.3",
+ "version": "0.1.4",
"type": "module",
"repository": {
"type": "git",
diff --git a/packages/wow-theme/src/tokens/typography.ts b/packages/wow-theme/src/tokens/typography.ts
index 55b7fb30..7a952cdc 100644
--- a/packages/wow-theme/src/tokens/typography.ts
+++ b/packages/wow-theme/src/tokens/typography.ts
@@ -38,4 +38,10 @@ export const textStyles = defineTextStyles({
label3: {
value: typography.label3,
},
+ header1: {
+ value: typography.header1,
+ },
+ header2: {
+ value: typography.header2,
+ },
});
diff --git a/packages/wow-theme/src/tokens/zIndex.ts b/packages/wow-theme/src/tokens/zIndex.ts
index 879fc4ab..628e6787 100644
--- a/packages/wow-theme/src/tokens/zIndex.ts
+++ b/packages/wow-theme/src/tokens/zIndex.ts
@@ -5,4 +5,7 @@ export const zIndex = defineTokens.zIndex({
dropdown: {
value: wowZIndex.dropdown,
},
+ overlay: {
+ value: wowZIndex.overlay,
+ },
});
diff --git a/packages/wow-tokens/CHANGELOG.md b/packages/wow-tokens/CHANGELOG.md
index 9f24f7d6..9e64a36a 100644
--- a/packages/wow-tokens/CHANGELOG.md
+++ b/packages/wow-tokens/CHANGELOG.md
@@ -1,5 +1,12 @@
# wowds-tokens
+## 0.1.4
+
+### Patch Changes
+
+- 185475a: zIndex 토큰을 추가합니다.
+- b7f51d2: Header 컴포넌트를 추가합니다.
+
## 0.1.3
### Patch Changes
diff --git a/packages/wow-tokens/package.json b/packages/wow-tokens/package.json
index fafa8808..85bc0480 100644
--- a/packages/wow-tokens/package.json
+++ b/packages/wow-tokens/package.json
@@ -1,6 +1,6 @@
{
"name": "wowds-tokens",
- "version": "0.1.3",
+ "version": "0.1.4",
"description": "",
"repository": {
"type": "git",
diff --git a/packages/wow-tokens/src/typography.ts b/packages/wow-tokens/src/typography.ts
index a643c400..fc8d9703 100644
--- a/packages/wow-tokens/src/typography.ts
+++ b/packages/wow-tokens/src/typography.ts
@@ -79,3 +79,15 @@ export const label3 = {
lineHeight: "100%",
fontWeight: 600,
};
+
+export const header1 = {
+ fontSize: "1.3rem",
+ lineHeight: "130%",
+ fontWeight: 700,
+};
+
+export const header2 = {
+ fontSize: "0.9rem",
+ lineHeight: "130%",
+ fontWeight: 400,
+};
diff --git a/packages/wow-tokens/src/zIndex.ts b/packages/wow-tokens/src/zIndex.ts
index fb2a7f3f..79e30bc3 100644
--- a/packages/wow-tokens/src/zIndex.ts
+++ b/packages/wow-tokens/src/zIndex.ts
@@ -1 +1,2 @@
export const dropdown = 10;
+export const overlay = 9999;
diff --git a/packages/wow-ui/.storybook/main.ts b/packages/wow-ui/.storybook/main.ts
index 35682223..e6c80f20 100644
--- a/packages/wow-ui/.storybook/main.ts
+++ b/packages/wow-ui/.storybook/main.ts
@@ -36,6 +36,28 @@ const config: StorybookConfig = {
"@styled-system": path.resolve(__dirname, "../styled-system"),
};
}
+
+ if (!config.module || !config.module.rules) {
+ return config;
+ }
+
+ config.module.rules = [
+ ...config.module.rules.map((rule) => {
+ if (!rule || rule === "...") {
+ return rule;
+ }
+
+ if (rule.test && /svg/.test(String(rule.test))) {
+ return { ...rule, exclude: /\.svg$/i };
+ }
+ return rule;
+ }),
+ {
+ test: /\.svg$/,
+ use: ["@svgr/webpack"],
+ },
+ ];
+
return config;
},
};
diff --git a/packages/wow-ui/CHANGELOG.md b/packages/wow-ui/CHANGELOG.md
index 9c09b59c..eeef1d82 100644
--- a/packages/wow-ui/CHANGELOG.md
+++ b/packages/wow-ui/CHANGELOG.md
@@ -1,5 +1,18 @@
# wowds-ui
+## 0.1.17
+
+### Patch Changes
+
+- 7b19f05: Tab 컴포넌트를 구현합니다.
+- 3682ddd: Avatar 컴포넌트를 추가합니다.
+- 185475a: Toast 컴포넌트를 추가합니다.
+- b7f51d2: Header 컴포넌트를 추가합니다.
+- Updated dependencies [3682ddd]
+- Updated dependencies [185475a]
+- Updated dependencies [b7f51d2]
+ - wowds-icons@0.1.5
+
## 0.1.16
### Patch Changes
diff --git a/packages/wow-ui/package.json b/packages/wow-ui/package.json
index dad39e09..468be460 100644
--- a/packages/wow-ui/package.json
+++ b/packages/wow-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "wowds-ui",
- "version": "0.1.16",
+ "version": "0.1.17",
"description": "",
"author": "gdsc-hongik",
"repository": {
@@ -20,6 +20,21 @@
"type": "module",
"exports": {
"./styles.css": "./dist/styles.css",
+ "./ToastProvider": {
+ "types": "./dist/components/Toast/ToastProvider.d.ts",
+ "require": "./dist/ToastProvider.cjs",
+ "import": "./dist/ToastProvider.js"
+ },
+ "./Toast": {
+ "types": "./dist/components/Toast/index.d.ts",
+ "require": "./dist/Toast.cjs",
+ "import": "./dist/Toast.js"
+ },
+ "./useToast": {
+ "types": "./dist/components/Toast/useToast.d.ts",
+ "require": "./dist/useToast.cjs",
+ "import": "./dist/useToast.js"
+ },
"./TextField": {
"types": "./dist/components/TextField/index.d.ts",
"require": "./dist/TextField.cjs",
@@ -65,6 +80,26 @@
"require": "./dist/Tr.cjs",
"import": "./dist/Tr.js"
},
+ "./Tabs": {
+ "types": "./dist/components/Tabs/index.d.ts",
+ "require": "./dist/Tabs.cjs",
+ "import": "./dist/Tabs.js"
+ },
+ "./TabsContent": {
+ "types": "./dist/components/Tabs/TabsContent.d.ts",
+ "require": "./dist/TabsContent.cjs",
+ "import": "./dist/TabsContent.js"
+ },
+ "./TabsItem": {
+ "types": "./dist/components/Tabs/TabsItem.d.ts",
+ "require": "./dist/TabsItem.cjs",
+ "import": "./dist/TabsItem.js"
+ },
+ "./TabsList": {
+ "types": "./dist/components/Tabs/TabsList.d.ts",
+ "require": "./dist/TabsList.cjs",
+ "import": "./dist/TabsList.js"
+ },
"./Switch": {
"types": "./dist/components/Switch/index.d.ts",
"require": "./dist/Switch.cjs",
@@ -135,6 +170,11 @@
"require": "./dist/MultiGroup.cjs",
"import": "./dist/MultiGroup.js"
},
+ "./Header": {
+ "types": "./dist/components/Header/index.d.ts",
+ "require": "./dist/Header.cjs",
+ "import": "./dist/Header.js"
+ },
"./DropDownOption": {
"types": "./dist/components/DropDown/DropDownOption.d.ts",
"require": "./dist/DropDownOption.cjs",
@@ -169,6 +209,11 @@
"types": "./dist/components/Box/index.d.ts",
"require": "./dist/Box.cjs",
"import": "./dist/Box.js"
+ },
+ "./Avatar": {
+ "types": "./dist/components/Avatar/index.d.ts",
+ "require": "./dist/Avatar.cjs",
+ "import": "./dist/Avatar.js"
}
},
"keywords": [],
@@ -210,12 +255,14 @@
"plop": "^4.0.1",
"rollup-plugin-peer-deps-external": "^2.2.4",
"storybook": "^8.1.9",
- "typescript": "^5.3.3"
+ "typescript": "^5.3.3",
+ "@types/uuid": "^10.0.0"
},
"dependencies": {
"clsx": "^2.1.1",
"lottie-react": "^2.4.0",
"react-day-picker": "^9.0.8",
+ "uuid": "^10.0.0",
"wowds-icons": "workspace:^"
},
"peerDependencies": {
diff --git a/packages/wow-ui/rollup.config.js b/packages/wow-ui/rollup.config.js
index e9210f7a..7bb9dfa3 100644
--- a/packages/wow-ui/rollup.config.js
+++ b/packages/wow-ui/rollup.config.js
@@ -20,6 +20,9 @@ process.env.BABEL_ENV = "production";
export default {
input: {
+ ToastProvider: "./src/components/Toast/ToastProvider",
+ Toast: "./src/components/Toast",
+ useToast: "./src/components/Toast/useToast",
TextField: "./src/components/TextField",
TextButton: "./src/components/TextButton",
Tag: "./src/components/Tag",
@@ -29,6 +32,10 @@ export default {
Th: "./src/components/Table/Th",
Thead: "./src/components/Table/Thead",
Tr: "./src/components/Table/Tr",
+ Tabs: "./src/components/Tabs",
+ TabsContent: "./src/components/Tabs/TabsContent",
+ TabsItem: "./src/components/Tabs/TabsItem",
+ TabsList: "./src/components/Tabs/TabsList",
Switch: "./src/components/Switch",
Stepper: "./src/components/Stepper",
BlueSpinner: "./src/components/Spinner/BlueSpinner",
@@ -43,6 +50,7 @@ export default {
TimePicker: "./src/components/Picker/TimePicker",
Pagination: "./src/components/Pagination",
MultiGroup: "./src/components/MultiGroup",
+ Header: "./src/components/Header",
DropDownOption: "./src/components/DropDown/DropDownOption",
DropDown: "./src/components/DropDown",
Divider: "./src/components/Divider",
@@ -50,6 +58,7 @@ export default {
Checkbox: "./src/components/Checkbox",
Button: "./src/components/Button",
Box: "./src/components/Box",
+ Avatar: "./src/components/Avatar",
},
output: [
{
diff --git a/packages/wow-ui/src/assets/lottie/blueSpinner.json b/packages/wow-ui/src/assets/lotties/blueSpinner.json
similarity index 100%
rename from packages/wow-ui/src/assets/lottie/blueSpinner.json
rename to packages/wow-ui/src/assets/lotties/blueSpinner.json
diff --git a/packages/wow-ui/src/assets/lottie/rainbowSpinner.json b/packages/wow-ui/src/assets/lotties/rainbowSpinner.json
similarity index 100%
rename from packages/wow-ui/src/assets/lottie/rainbowSpinner.json
rename to packages/wow-ui/src/assets/lotties/rainbowSpinner.json
diff --git a/packages/wow-ui/src/components/Avatar/Avatar.stories.tsx b/packages/wow-ui/src/components/Avatar/Avatar.stories.tsx
new file mode 100644
index 00000000..bcedd31c
--- /dev/null
+++ b/packages/wow-ui/src/components/Avatar/Avatar.stories.tsx
@@ -0,0 +1,119 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import Avatar from "@/components/Avatar/index.tsx";
+
+const meta = {
+ title: "UI/Avatar",
+ component: Avatar,
+ parameters: {
+ componentSubtitle: "아바타 컴포넌트",
+ },
+ tags: ["autodocs"],
+ argTypes: {
+ size: {
+ control: { type: "radio" },
+ options: ["sm", "lg"],
+ description: "아바타의 크기입니다.",
+ table: {
+ defaultValue: { summary: "lg" },
+ type: {
+ summary: "sm | lg",
+ },
+ },
+ },
+ variant: {
+ control: { type: "radio" },
+ options: ["blue", "green", "yellow", "red"],
+ description: "아바타의 색상입니다.",
+ table: {
+ defaultValue: { summary: "blue" },
+ type: {
+ summary: "blue | green | yellow | red",
+ },
+ },
+ },
+ ImageComponent: {
+ control: false,
+ description: "아바타에 표시할 이미지 컴포넌트입니다.",
+ table: {
+ type: { summary: "ElementType" },
+ },
+ },
+ imageUrl: {
+ control: { type: "text" },
+ description: "아바타에 표시할 이미지의 URL입니다.",
+ table: {
+ type: { summary: "string" },
+ },
+ },
+ username: {
+ control: { type: "text" },
+ description: "아바타 옆에 표시할 사용자 이름입니다.",
+ table: {
+ type: { summary: "string" },
+ },
+ },
+ orientation: {
+ control: { type: "radio" },
+ options: ["left", "right"],
+ description:
+ "사용자 이름 레이블의 방향입니다. size가 'sm'인 경우 지정할 수 있습니다.",
+ table: {
+ defaultValue: { summary: "right" },
+ type: { summary: "left | right" },
+ },
+ },
+ asProp: {
+ control: false,
+ description: "렌더링할 HTML 요소나 React 컴포넌트입니다.",
+ table: {
+ defaultValue: { summary: "div" },
+ type: { summary: "React.ElementType" },
+ },
+ },
+ ref: {
+ description: "'ref'를 사용하여 컴포넌트에 직접 접근할 수 있습니다.",
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {},
+};
+
+export const Small: Story = {
+ args: {
+ size: "sm",
+ },
+};
+
+export const WithImage: Story = {
+ args: {
+ imageUrl: "https://i.ibb.co/2hz6hNP/2024-06-06-11-52-22.png",
+ },
+};
+
+export const LargeWithUsername: Story = {
+ args: {
+ username: "김홍익 님",
+ },
+};
+
+export const SmallWithUsernameOrientationLeft: Story = {
+ args: {
+ username: "김홍익 님",
+ size: "sm",
+ orientation: "left",
+ },
+};
+
+export const SmallWithUsernameOrientationRight: Story = {
+ args: {
+ username: "김홍익 님",
+ size: "sm",
+ },
+};
diff --git a/packages/wow-ui/src/components/Avatar/index.tsx b/packages/wow-ui/src/components/Avatar/index.tsx
new file mode 100644
index 00000000..018c6134
--- /dev/null
+++ b/packages/wow-ui/src/components/Avatar/index.tsx
@@ -0,0 +1,163 @@
+import { cva } from "@styled-system/css";
+import type { ElementType, PropsWithChildren, ReactNode } from "react";
+import { forwardRef } from "react";
+import { BlueAvatar, GreenAvatar, RedAvatar, YellowAvatar } from "wowds-icons";
+
+import type {
+ PolymorphicComponentProps,
+ PolymorphicComponentPropsWithRef,
+ PolymorphicRef,
+} from "@/types/polymorphic.ts";
+
+/**
+ * @description Avatar 컴포넌트는 사용자 프로필을 나타내는 컴포넌트입니다.
+ *
+ * @param {("sm" | "lg")} [size="lg"] - 아바타의 크기.
+ * @param {("blue" | "green" | "yellow" | "red")} [variant="blue"] - 아바타의 색상.
+ * @param {ElementType} [ImageComponent] - 아바타에 표시할 이미지 컴포넌트.
+ * @param {string} [imageUrl] - 아바타에 표시할 이미지의 URL.
+ * @param {string} [username] - 아바타 옆에 표시할 사용자 이름. 사용자 이름이 제공되면 레이블로 표시됨.
+ * @param {("left" | "right")} [orientation="right"] - 사용자 이름 레이블의 방향. size가 'sm'인 경우 지정 가능.
+ * @param {React.ElementType} [asProp="div"] - 렌더링할 HTML 요소나 React 컴포넌트. 기본값은 "div".
+ */
+
+type AvatarSizeType = "sm" | "lg";
+
+export interface _AvatarProps {
+ size?: AvatarSizeType;
+ variant?: "blue" | "green" | "yellow" | "red";
+ ImageComponent?: ElementType;
+ imageUrl?: string;
+ username?: string;
+ orientation?: "left" | "right";
+}
+
+type AvatarProps = PolymorphicComponentProps<
+ T,
+ _AvatarProps
+> &
+ PropsWithChildren;
+
+type AvatarComponent = (
+ props: PolymorphicComponentPropsWithRef>
+) => ReactNode;
+
+const Avatar: AvatarComponent & { displayName?: string } = forwardRef(
+ (
+ {
+ asProp,
+ size = "lg",
+ variant = "blue",
+ ImageComponent,
+ imageUrl,
+ username,
+ orientation = "right",
+ ...rest
+ }: AvatarProps,
+ ref?: PolymorphicRef
+ ) => {
+ const Component = asProp || "div";
+ const AvatarComponent = avatarMap[variant];
+
+ return (
+
+ {ImageComponent ? (
+
+ ) : imageUrl ? (
+
+ ) : (
+
+ )}
+
+
+ );
+ }
+);
+
+Avatar.displayName = "Avatar";
+export default Avatar;
+
+const avatarMap = {
+ blue: BlueAvatar,
+ green: GreenAvatar,
+ red: RedAvatar,
+ yellow: YellowAvatar,
+};
+
+const avatarContainerStyle = cva({
+ base: {
+ height: "fit-content",
+ width: "fit-content",
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ variants: {
+ size: {
+ sm: {
+ gap: 8,
+ },
+ lg: {
+ gap: 12,
+ },
+ },
+ orientation: {
+ default: {
+ flexDirection: "column",
+ },
+ left: {
+ flexDirection: "row-reverse",
+ },
+ right: {
+ flexDirection: "row",
+ },
+ },
+ },
+});
+
+const avatarSizeStyle = cva({
+ base: {
+ borderRadius: "50%",
+ },
+ variants: {
+ size: {
+ sm: {
+ width: 32,
+ height: 32,
+ },
+ lg: {
+ width: 100,
+ height: 100,
+ },
+ },
+ },
+});
+
+const avatarLabelStyle = cva({
+ variants: {
+ size: {
+ sm: {
+ textStyle: "label1",
+ },
+ lg: {
+ textStyle: "h1",
+ },
+ },
+ },
+});
diff --git a/packages/wow-ui/src/components/Checkbox/index.tsx b/packages/wow-ui/src/components/Checkbox/index.tsx
index 6e8169b7..325cd822 100644
--- a/packages/wow-ui/src/components/Checkbox/index.tsx
+++ b/packages/wow-ui/src/components/Checkbox/index.tsx
@@ -116,7 +116,7 @@ const Checkbox = forwardRef(
})}
{...inputProps}
value={value}
- onClick={() => handleClick(value)}
+ onChange={() => handleClick(value)}
/>
{checked && (
;
+
+export default meta;
+
+export const Default: StoryObj = {
+ args: {},
+};
+
+export const Login: StoryObj = {
+ args: {
+ variant: "login",
+ },
+};
+
+export const Username: StoryObj = {
+ args: {
+ variant: "username",
+ username: "김홍익",
+ },
+};
diff --git a/packages/wow-ui/src/components/Header/index.tsx b/packages/wow-ui/src/components/Header/index.tsx
new file mode 100644
index 00000000..f505748e
--- /dev/null
+++ b/packages/wow-ui/src/components/Header/index.tsx
@@ -0,0 +1,115 @@
+/**
+ * @description 헤더 컴포넌트입니다.
+ * 사이트 로고와 로그인 또는 사용자 이름 표시 기능을 포함합니다.
+ *
+ * @template T
+ * @param {"username" | "login" | "none"} [variant] - Header 종류.
+ * - "username": 사용자 이름을 표시.
+ * - "login": 로그인 버튼을 표시.
+ * - "none": 아무것도 표시하지 않음.
+ * @param {T extends "username" ? string : never} [username] - variant가 "username"인 경우 표시되는 오른쪽 요소. 사용자가 로그인한 경우 사용자 이름을 표시함.
+ * @param {T extends "login" ? () => void : never} [onClick] - 로그인 버튼 클릭 시 호출되는 함수.
+ */
+
+import { css } from "@styled-system/css";
+import { Flex } from "@styled-system/jsx";
+import { GdscLogo } from "wowds-icons";
+
+import Button from "@/components/Button";
+
+type RightElementType = "username" | "login" | "none";
+
+export interface HeaderProps {
+ variant?: T;
+ username?: T extends "username" ? string : never;
+ onClick?: T extends "login" ? () => void : never;
+}
+
+const Header = ({
+ variant = "none",
+ username,
+ onClick,
+}: HeaderProps) => {
+ return (
+
+
+
+
+
+ GDSC
+ Hongik Univ.
+
+
+
+ {variant === "login" && (
+
+ )}
+ {variant === "username" && (
+
+ {username} 님
+
+ )}
+
+
+
+ );
+};
+
+export default Header;
+
+const headerStyle = css({
+ borderBottomStyle: "solid",
+ borderBottomWidth: 1,
+ borderBottomColor: "outline",
+ height: "54px",
+ width: "100%",
+ display: "flex",
+ alignItems: "center",
+ xsToLg: {
+ paddingX: "16px",
+ height: "66px",
+ },
+});
+
+const headerContentStyle = css({
+ width: "988px",
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ margin: "auto",
+});
+
+const leftElementContainerStyle = css({
+ display: "flex",
+ gap: "xs",
+ alignItems: "center",
+});
+
+const titleStyle = css({
+ fontFamily: "Product Sans",
+ textStyle: "header1",
+});
+
+const subTitleStyle = css({
+ fontFamily: "Product Sans",
+ color: "primary",
+ textStyle: "header2",
+});
+
+const usernameTextStyle = css({
+ textStyle: "label1",
+});
+
+const logoTextContainerStyle = css({
+ lg: {
+ gap: "xs",
+ alignItems: "center",
+ },
+ xsToLg: {
+ flexDirection: "column",
+ justifyContent: "flex-start",
+ alignItems: "space-between",
+ },
+});
diff --git a/packages/wow-ui/src/components/Spinner/BlueSpinner.tsx b/packages/wow-ui/src/components/Spinner/BlueSpinner.tsx
index 354d26b6..c2be21f5 100644
--- a/packages/wow-ui/src/components/Spinner/BlueSpinner.tsx
+++ b/packages/wow-ui/src/components/Spinner/BlueSpinner.tsx
@@ -2,7 +2,7 @@ import type { LottieComponentProps } from "lottie-react";
import Lottie from "lottie-react";
import type { CSSProperties } from "react";
-import blueSpinner from "@/assets/lottie/blueSpinner.json";
+import blueSpinner from "@/assets/lotties/blueSpinner.json";
/**
* @description 블루 스피너 컴포넌트입니다.
diff --git a/packages/wow-ui/src/components/Spinner/RainbowSpinner.tsx b/packages/wow-ui/src/components/Spinner/RainbowSpinner.tsx
index 5020d788..e1731fca 100644
--- a/packages/wow-ui/src/components/Spinner/RainbowSpinner.tsx
+++ b/packages/wow-ui/src/components/Spinner/RainbowSpinner.tsx
@@ -2,7 +2,7 @@ import type { LottieComponentProps } from "lottie-react";
import Lottie from "lottie-react";
import type { CSSProperties } from "react";
-import rainbowSpinner from "@/assets/lottie/rainbowSpinner.json";
+import rainbowSpinner from "@/assets/lotties/rainbowSpinner.json";
/**
* @description 레인보우 스피너 컴포넌트입니다. Lottie 애니메이션을 사용하여 스피너를 표시합니다.
diff --git a/packages/wow-ui/src/components/Switch/index.tsx b/packages/wow-ui/src/components/Switch/index.tsx
index 46af598c..b36631e8 100644
--- a/packages/wow-ui/src/components/Switch/index.tsx
+++ b/packages/wow-ui/src/components/Switch/index.tsx
@@ -106,7 +106,7 @@ const Switch = forwardRef(
ref={ref}
type="checkbox"
value={value}
- onClick={() => handleClick(value)}
+ onChange={() => handleClick(value)}
{...inputProps}
/>
diff --git a/packages/wow-ui/src/components/Tabs/Tabs.stories.tsx b/packages/wow-ui/src/components/Tabs/Tabs.stories.tsx
new file mode 100644
index 00000000..23020448
--- /dev/null
+++ b/packages/wow-ui/src/components/Tabs/Tabs.stories.tsx
@@ -0,0 +1,196 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { useState } from "react";
+
+import type { TabsProps } from "@/components/Tabs";
+import Tabs from "@/components/Tabs";
+import TabsContent from "@/components/Tabs/TabsContent";
+import TabsItem from "@/components/Tabs/TabsItem";
+import TabsList from "@/components/Tabs/TabsList";
+
+const meta: Meta = {
+ title: "UI/Tabs",
+ component: Tabs,
+ tags: ["autodocs"],
+ parameters: {
+ componentSubtitle: "탭을 통해 콘텐츠를 선택할 수 있는 컴포넌트입니다.",
+ docs: {
+ description: {
+ component:
+ "TabsList 로 TabsItem을 감싸서 탭 트리거를 관리하고 TabsContent 로 탭 콘텐츠를 관리합니다.",
+ },
+ },
+ a11y: {
+ config: {
+ rules: [{ id: "color-contrast", enabled: false }],
+ },
+ },
+ },
+ argTypes: {
+ children: {
+ description: "TabsList,TabsItem,TabsContent 를 children 으로 받습니다.",
+ table: {
+ type: { summary: "ReactNode" },
+ },
+ control: false,
+ },
+ value: {
+ description: "현재 선택된 탭의 값을 나타냅니다.",
+ table: {
+ type: { summary: "string" },
+ },
+ control: "text",
+ },
+ defaultValue: {
+ description: "초기 선택된 탭 값을 나타냅니다.",
+ table: {
+ type: { summary: "string" },
+ },
+ control: "text",
+ },
+ onChange: {
+ description: "탭 값이 변경될 때 호출되는 함수입니다.",
+ table: {
+ type: { summary: "(value: string) => void" },
+ },
+ action: "changed",
+ },
+ label: {
+ description: "각 탭을 구분할 수 있는 레이블입니다.",
+ table: {
+ type: { summary: "string" },
+ },
+ control: "text",
+ },
+ style: {
+ description: "탭의 커스텀 스타일을 설정합니다.",
+ table: {
+ type: { summary: "CSSProperties" },
+ defaultValue: { summary: "{}" },
+ },
+ control: false,
+ },
+ className: {
+ description: "탭에 전달하는 커스텀 클래스를 설정합니다.",
+ table: {
+ type: { summary: "string" },
+ },
+ control: {
+ type: "text",
+ },
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Primary: Story = {
+ args: {
+ children: (
+ <>
+
+ Tab 1
+ Tab 2
+
+ Tab 1 Content
+ Tab 2 Content
+ >
+ ),
+ defaultValue: "tab1",
+ },
+ parameters: {
+ docs: {
+ description: {
+ story: "기본적인 탭 컴포넌트입니다. 탭 1과 탭 2가 제공됩니다.",
+ },
+ },
+ },
+};
+
+export const WithDefaultValue: Story = {
+ args: {
+ children: (
+ <>
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+ Tab 1 Content
+ Tab 2 Content
+ Tab 3 Content
+ >
+ ),
+ defaultValue: "tab2",
+ },
+ parameters: {
+ docs: {
+ description: {
+ story:
+ "초기 값으로 두 번째 탭이 선택된 상태로 시작하는 컴포넌트입니다.",
+ },
+ },
+ },
+};
+
+const ControlledTabsComponent = () => {
+ const [selectedTab, setSelectedTab] = useState("tab1");
+
+ const handleChange = (value: string) => {
+ setSelectedTab(value);
+ };
+
+ return (
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+ Tab 1 Content
+ Tab 2 Content
+ Tab 3 Content
+
+ );
+};
+
+export const ControlledValue: Story = {
+ render: () => ,
+ parameters: {
+ docs: {
+ description: {
+ story: "외부 상태에 따라 제어되는 탭 컴포넌트입니다.",
+ },
+ },
+ },
+};
+
+export const ManyTabs: Story = {
+ args: {
+ children: (
+ <>
+
+ {Array.from({ length: 10 }, (_, index) => (
+
+ Tab {index + 1}
+
+ ))}
+
+ {Array.from({ length: 10 }, (_, index) => (
+
+ Tab {index + 1} Content
+
+ ))}
+ >
+ ),
+ defaultValue: "tab1",
+ },
+ parameters: {
+ docs: {
+ description: {
+ story: "여러 개의 탭을 가진 탭 컴포넌트입니다.",
+ },
+ },
+ },
+};
diff --git a/packages/wow-ui/src/components/Tabs/Tabs.test.tsx b/packages/wow-ui/src/components/Tabs/Tabs.test.tsx
new file mode 100644
index 00000000..32b9c992
--- /dev/null
+++ b/packages/wow-ui/src/components/Tabs/Tabs.test.tsx
@@ -0,0 +1,87 @@
+import type { RenderResult } from "@testing-library/react";
+import { render, waitFor } from "@testing-library/react";
+import { userEvent } from "@testing-library/user-event";
+
+import type { TabsProps } from "@/components/Tabs";
+import Tabs from "@/components/Tabs";
+import TabsContent from "@/components/Tabs/TabsContent";
+import TabsItem from "@/components/Tabs/TabsItem";
+import TabsList from "@/components/Tabs/TabsList";
+
+describe("Tabs component", () => {
+ const uncontrolledTabs = (props: Partial = {}): RenderResult => {
+ return render(
+
+
+ Tab 1
+ Tab 2
+
+ Tab 1 Content
+ Tab 2 Content
+
+ );
+ };
+
+ const controlledTabs = (props: Partial = {}): RenderResult => {
+ return render(
+
+
+ Tab 1
+ Tab 2
+
+ Tab 1 Content
+ Tab 2 Content
+
+ );
+ };
+ test("renders correctly with default value", async () => {
+ const { getByText } = uncontrolledTabs();
+ expect(getByText("Tab 1 Content")).toBeVisible();
+ });
+
+ test("switches content when clicking on tab triggers", async () => {
+ const { getByText } = uncontrolledTabs();
+ await userEvent.click(getByText("Tab 2"));
+ await waitFor(() => {
+ expect(getByText("Tab 2 Content")).toBeVisible();
+ });
+ });
+
+ test("calls onChange when the tab is changed", async () => {
+ const handleChange = jest.fn();
+ const { getByText } = controlledTabs({
+ value: "tab1",
+ onChange: handleChange,
+ });
+ await userEvent.click(getByText("Tab 2"));
+ expect(handleChange).toHaveBeenCalledWith("tab2");
+ });
+
+ test("can navigate between tabs using keyboard (ArrowRight)", async () => {
+ const { getByText } = uncontrolledTabs();
+ const tab1 = getByText("Tab 1");
+ const tab2 = getByText("Tab 2");
+
+ tab1.focus();
+ await userEvent.keyboard("{ArrowRight}");
+ expect(tab2).toHaveFocus();
+
+ await waitFor(() => {
+ expect(getByText("Tab 2 Content")).toBeVisible();
+ });
+ });
+
+ test("can navigate between tabs using keyboard (ArrowLeft)", async () => {
+ const { getByText } = uncontrolledTabs();
+ const tab1 = getByText("Tab 1");
+ const tab2 = getByText("Tab 2");
+
+ tab1.focus();
+ await userEvent.keyboard("{ArrowLeft}");
+ expect(tab2).toHaveFocus();
+
+ await waitFor(() => {
+ expect(getByText("Tab 2 Content")).toBeVisible();
+ });
+ });
+});
diff --git a/packages/wow-ui/src/components/Tabs/TabsContent.tsx b/packages/wow-ui/src/components/Tabs/TabsContent.tsx
new file mode 100644
index 00000000..ce6234a5
--- /dev/null
+++ b/packages/wow-ui/src/components/Tabs/TabsContent.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import type { PropsWithChildren } from "react";
+import { forwardRef } from "react";
+
+import type { DefaultProps } from "@/types/DefaultProps";
+
+import { useTabsContext } from "./contexts/TabsContext";
+
+/**
+ * @description TabsContent 컴포넌트는 각 Tab에 해당하는 콘텐츠입니다.
+ * @param {string} value - TabTrigger의 value와 일치하는 값입니다.
+ * @param {string} [className] - TabsContent에 전달할 커스텀 클래스.
+ * @param {CSSProperties} [style] - TabsContent에 전달할 커스텀 스타일.
+ * @param {ComponentPropsWithoutRef} rest 렌더링된 요소 또는 컴포넌트에 전달할 추가 props.
+ * @param {ComponentPropsWithRef["ref"]} ref 렌더링된 요소 또는 컴포넌트에 연결할 ref.
+ * @param {ReactNode} children - TabsContent의 자식 요소.
+ */
+interface TabsContentProps extends PropsWithChildren, DefaultProps {
+ value: string;
+}
+
+const TabsContent = forwardRef(
+ ({ value: tabValue, children }: TabsContentProps, ref) => {
+ const { value, label } = useTabsContext();
+ const selected = tabValue === value;
+ if (!selected) return null;
+
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+export default TabsContent;
diff --git a/packages/wow-ui/src/components/Tabs/TabsItem.tsx b/packages/wow-ui/src/components/Tabs/TabsItem.tsx
new file mode 100644
index 00000000..92e15ca2
--- /dev/null
+++ b/packages/wow-ui/src/components/Tabs/TabsItem.tsx
@@ -0,0 +1,100 @@
+"use client";
+
+import { cva } from "@styled-system/css";
+import { clsx } from "clsx";
+import type { ButtonHTMLAttributes, PropsWithChildren } from "react";
+import { forwardRef, useEffect, useRef } from "react";
+
+import { useMergeRefs } from "@/hooks/useMergeRefs";
+import type { DefaultProps } from "@/types/DefaultProps";
+
+import { useCollectionContext } from "./contexts/CollectionContext";
+import { useTabsContext } from "./contexts/TabsContext";
+
+/**
+ * @description TabsItem 컴포넌트는 각 Tab 컴포넌트입니다.
+ * @param {string} value - TabsContent의 value와 일치하는 값입니다.
+ * @param {ReactNode} children - TabsContent 자식 요소.
+ * @param {string} [className] - TabsItem에 전달할 커스텀 클래스.
+ * @param {CSSProperties} [style] - TabsItem에 전달할 커스텀 스타일.
+ * @param {ComponentPropsWithoutRef} rest 렌더링된 요소 또는 컴포넌트에 전달할 추가 props.
+ * @param {ComponentPropsWithRef["ref"]} ref 렌더링된 요소 또는 컴포넌트에 연결할 ref.
+ * @param {ReactNode} children - TabsItem의 자식 요소.
+ */
+interface TabsItemProps
+ extends PropsWithChildren,
+ DefaultProps,
+ ButtonHTMLAttributes {
+ value: string;
+}
+
+const TabsItem = forwardRef(
+ ({ value, children, className, ...rest }: TabsItemProps, ref) => {
+ const { value: selectedValue, setSelectedValue, label } = useTabsContext();
+ const selected = selectedValue === value;
+
+ const handleClickTabTrigger = () => {
+ setSelectedValue(value);
+ };
+
+ const { values } = useCollectionContext();
+ const internalButtonRef = useRef(null);
+ const buttonRef = useMergeRefs(ref, internalButtonRef);
+
+ useEffect(() => {
+ values.add(value);
+ if (selected && internalButtonRef.current) {
+ internalButtonRef.current.focus();
+ }
+ }, [values, selected, value]);
+
+ return (
+
+ );
+ }
+);
+export default TabsItem;
+
+const tabItemStyle = cva({
+ base: {
+ textStyle: "label1",
+ paddingY: "sm",
+ paddingX: "14px",
+ borderBottom: "1px solid",
+ borderColor: "outline",
+ color: "sub",
+ outline: "none",
+ cursor: "pointer",
+ whiteSpace: "pre",
+ xsToSm: {
+ display: "flex",
+ flexGrow: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ },
+ variants: {
+ type: {
+ selected: {
+ color: "primary",
+ borderColor: "primary",
+ },
+ default: {},
+ },
+ },
+});
diff --git a/packages/wow-ui/src/components/Tabs/TabsList.tsx b/packages/wow-ui/src/components/Tabs/TabsList.tsx
new file mode 100644
index 00000000..ed02fee9
--- /dev/null
+++ b/packages/wow-ui/src/components/Tabs/TabsList.tsx
@@ -0,0 +1,98 @@
+"use client";
+
+import { css } from "@styled-system/css";
+import { Flex } from "@styled-system/jsx";
+import {
+ type KeyboardEvent,
+ type PropsWithChildren,
+ useCallback,
+ useEffect,
+} from "react";
+
+import { useCollectionContext } from "./contexts/CollectionContext";
+import { useTabsContext } from "./contexts/TabsContext";
+
+/**
+ * @description TabsList 컴포넌트는 TabsItem 컴포넌트들을 관리합니다.
+ */
+const TabsList = ({ children }: PropsWithChildren) => {
+ const {
+ label,
+ setSelectedValue,
+ value: selectedValue,
+ isControlled,
+ } = useTabsContext();
+
+ const { values } = useCollectionContext();
+
+ useEffect(() => {
+ if (!isControlled && !selectedValue && values.size > 0) {
+ setSelectedValue(values.values().next().value);
+ }
+ }, []);
+
+ const updateFocusedValue = useCallback(
+ (direction: number) => {
+ const valuesArray = Array.from(values);
+ const currentIndex = valuesArray.indexOf(selectedValue ?? "");
+ const nextIndex =
+ (currentIndex + direction + valuesArray.length) % valuesArray.length;
+ setSelectedValue(valuesArray[nextIndex] ?? "");
+ },
+ [setSelectedValue, selectedValue, values]
+ );
+
+ const handleArrowNavigation = useCallback(
+ (direction: number, event: KeyboardEvent) => {
+ updateFocusedValue(direction);
+ event.preventDefault();
+ },
+ [updateFocusedValue]
+ );
+
+ const handleKeyDown = useCallback(
+ (event: KeyboardEvent) => {
+ const { key } = event;
+
+ if (key === "ArrowRight") {
+ handleArrowNavigation(1, event);
+ } else if (key === "ArrowLeft") {
+ handleArrowNavigation(-1, event);
+ }
+ },
+ [handleArrowNavigation]
+ );
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default TabsList;
+
+const tabsListStyle = css({
+ overflowX: "scroll",
+ scrollBehavior: "smooth",
+ _scrollbar: {
+ width: "65px",
+ height: "2px",
+ },
+ _scrollbarThumb: {
+ width: "65px",
+ height: "2px",
+ borderRadius: "sm",
+ backgroundColor: "outline",
+ },
+ _scrollbarTrack: {
+ marginTop: "2px",
+ marginBottom: "2px",
+ },
+});
diff --git a/packages/wow-ui/src/components/Tabs/contexts/CollectionContext.tsx b/packages/wow-ui/src/components/Tabs/contexts/CollectionContext.tsx
new file mode 100644
index 00000000..f0ce0217
--- /dev/null
+++ b/packages/wow-ui/src/components/Tabs/contexts/CollectionContext.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import type { PropsWithChildren } from "react";
+import { createContext } from "react";
+
+import useSafeContext from "@/hooks/useSafeContext";
+
+interface CollectionContextProps {
+ values: Set;
+}
+
+const CollectionContext = createContext(null);
+
+export const useCollectionContext = () => {
+ const context = useSafeContext(CollectionContext);
+ return context;
+};
+
+export const CollectionProvider = ({ children }: PropsWithChildren) => {
+ return (
+ () }}>
+ {children}
+
+ );
+};
diff --git a/packages/wow-ui/src/components/Tabs/contexts/TabsContext.ts b/packages/wow-ui/src/components/Tabs/contexts/TabsContext.ts
new file mode 100644
index 00000000..60259df4
--- /dev/null
+++ b/packages/wow-ui/src/components/Tabs/contexts/TabsContext.ts
@@ -0,0 +1,19 @@
+"use client";
+
+import { createContext } from "react";
+
+import useSafeContext from "@/hooks/useSafeContext";
+
+interface TabsContextProps {
+ value: string;
+ setSelectedValue: (value: string) => void;
+ label?: string;
+ isControlled: boolean;
+}
+
+export const TabsContext = createContext(null);
+
+export const useTabsContext = () => {
+ const context = useSafeContext(TabsContext);
+ return context;
+};
diff --git a/packages/wow-ui/src/components/Tabs/index.tsx b/packages/wow-ui/src/components/Tabs/index.tsx
new file mode 100644
index 00000000..50edce44
--- /dev/null
+++ b/packages/wow-ui/src/components/Tabs/index.tsx
@@ -0,0 +1,70 @@
+"use client";
+
+import { css } from "@styled-system/css";
+import { clsx } from "clsx";
+import type { PropsWithChildren } from "react";
+import { useRef, useState } from "react";
+
+import { CollectionProvider } from "@/components/Tabs/contexts/CollectionContext";
+import { TabsContext } from "@/components/Tabs/contexts/TabsContext";
+import type { DefaultProps } from "@/types/DefaultProps";
+
+/**
+ * @description Tabs 컴포넌트는 탭을 통해 콘텐츠를 선택할 수 있는 컴포넌트입니다.
+ * @param {string} [defaultValue] - 탭의 기본값입니다.
+ * @param {string} [value] - 현재 선택된 탭의 값입니다.
+ * @param {string} [label] - 각 탭을 구분할 수 있는 레이블입니다.
+ * @param {(value: string) => void} [onChange] - 탭이 변경될 때 호출되는 함수입니다.
+ * @param {CSSProperties} [style] - 탭 컴포넌트의 커스텀 스타일.
+ * @param {string} [className] - 탭 컴포넌트에 전달할 커스텀 클래스.
+ * @param {ReactNode} children - 탭의 자식 요소.
+ */
+export interface TabsProps extends PropsWithChildren, DefaultProps {
+ defaultValue?: string;
+ value?: string;
+ label?: string;
+ onChange?: (value: string) => void;
+}
+const Tabs = ({
+ defaultValue,
+ value: valueProp,
+ label = "default-tab",
+ children,
+ onChange,
+ className,
+ style,
+}: TabsProps) => {
+ const [selectedValue, setSelectedValue] = useState(defaultValue ?? "");
+ const isControlled = useRef(valueProp !== undefined).current;
+
+ const handleSelect = (selectedValue: string) => {
+ if (!isControlled) {
+ setSelectedValue(selectedValue);
+ return;
+ }
+ if (onChange) {
+ onChange(selectedValue);
+ }
+ };
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export default Tabs;
+
+const tabsContainerStyle = css({
+ width: "100%",
+});
diff --git a/packages/wow-ui/src/components/Toast/Toast.stories.tsx b/packages/wow-ui/src/components/Toast/Toast.stories.tsx
new file mode 100644
index 00000000..11451a3f
--- /dev/null
+++ b/packages/wow-ui/src/components/Toast/Toast.stories.tsx
@@ -0,0 +1,173 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { useEffect } from "react";
+import { Warn } from "wowds-icons";
+
+import Button from "@/components/Button";
+
+import Toast from ".";
+import ToastProvider from "./ToastProvider";
+import useToast from "./useToast";
+
+const meta: Meta = {
+ title: "UI/Toast",
+ component: Toast,
+ tags: ["autodocs"],
+ parameters: {
+ componentSubtitle: "Toast 컴포넌트",
+ a11y: {
+ config: {
+ rules: [{ id: "color-contrast", enabled: false }],
+ },
+ },
+ docs: {
+ description: {
+ component:
+ "토스트가 필요한 레이아웃에서 children을 ToastProvider로 감싸 사용합니다.",
+ },
+ },
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+ argTypes: {
+ text: {
+ description: "Toast에 들어갈 메인 텍스트를 나타냅니다.",
+ control: { type: "text" },
+ },
+ subText: {
+ description: "Toast에 들어갈 보조 텍스트를 나타냅니다.",
+ control: { type: "text" },
+ },
+ rightIcon: {
+ description: "Toast의 우측에 들어갈 아이콘을 나타냅니다.",
+ table: {
+ type: { summary: "none | close | arrow" },
+ defaultValue: { summary: "none" },
+ },
+ control: "radio",
+ options: ["none", "close", "arrow"],
+ },
+ id: {
+ description: "Toast 컴포넌트의 id를 나타냅니다.",
+ control: false,
+ },
+ onClickArrowIcon: {
+ description:
+ "Toast 컴포넌트의 화살표 아이콘을 클릭했을 때 호출되는 함수를 나타냅니다.",
+ control: false,
+ },
+ onRemove: {
+ description: "Toast 컴포넌트가 닫힌 이후 호출되는 함수를 나타냅니다.",
+ control: false,
+ },
+ showLeftIcon: {
+ description: "Toast 좌측에 들어갈 아이콘의 노출 여부를 나타냅니다.",
+ control: "boolean",
+ },
+ toastDuration: {
+ description: "Toast가 보여지는 시간(ms)을 나타냅니다.",
+ control: { type: "number" },
+ },
+ style: {
+ description: "Toast에 커스텀 스타일을 적용하기 위한 객체를 나타냅니다.",
+ control: false,
+ },
+ className: {
+ description: "Toast에 커스텀 클래스를 적용하기 위한 문자열을 나타냅니다.",
+ control: false,
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ id: "1",
+ text: "Text",
+ subText: "subtext",
+ toastDuration: 60 * 60 * 1000,
+ },
+};
+
+export const WithTrigger = () => {
+ const { toast } = useToast();
+
+ return (
+
+ );
+};
+
+export const WithCloseIcon = () => {
+ const { toast } = useToast();
+ useEffect(() => {
+ toast({
+ text: "Text",
+ subText: "subtext",
+ rightIcon: "close",
+ });
+ }, []);
+};
+
+export const WithArrowIcon = () => {
+ const { toast } = useToast();
+ useEffect(() => {
+ toast({
+ text: "Text",
+ subText: "subtext",
+ rightIcon: "arrow",
+ });
+ }, []);
+};
+
+export const WithLeftIcon = () => {
+ const { toast } = useToast();
+ useEffect(() => {
+ toast({
+ text: "Text",
+ subText: "subtext",
+ showLeftIcon: true,
+ });
+ }, []);
+};
+
+export const WithLeftAndArrowIcons = () => {
+ const { toast } = useToast();
+ useEffect(() => {
+ toast({
+ text: "Text",
+ subText: "subtext",
+ showLeftIcon: true,
+ rightIcon: "arrow",
+ });
+ }, []);
+};
+
+export const TwoLines = () => {
+ const { toast } = useToast();
+ useEffect(() => {
+ toast({
+ showLeftIcon: true,
+ text: "TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText",
+ });
+ }, []);
+};
+
+export const Slow = () => {
+ const { toast } = useToast();
+ useEffect(() => {
+ toast({
+ text: "Text",
+ subText: "subtext",
+ toastDuration: 5000,
+ });
+ }, []);
+};
diff --git a/packages/wow-ui/src/components/Toast/ToastContext.ts b/packages/wow-ui/src/components/Toast/ToastContext.ts
new file mode 100644
index 00000000..aff6f0f2
--- /dev/null
+++ b/packages/wow-ui/src/components/Toast/ToastContext.ts
@@ -0,0 +1,22 @@
+import { createContext } from "react";
+
+import useSafeContext from "@/hooks/useSafeContext";
+
+import type { ToastProps } from ".";
+
+interface ToastContextProps {
+ toasts: ToastProps[];
+ addToast: (
+ toast: Omit & Partial>
+ ) => void;
+ removeToast: (id: string) => void;
+}
+
+export const ToastContext = createContext(
+ undefined
+);
+
+export const useToastContext = () => {
+ const context = useSafeContext(ToastContext);
+ return context;
+};
diff --git a/packages/wow-ui/src/components/Toast/ToastProvider.tsx b/packages/wow-ui/src/components/Toast/ToastProvider.tsx
new file mode 100644
index 00000000..aa766870
--- /dev/null
+++ b/packages/wow-ui/src/components/Toast/ToastProvider.tsx
@@ -0,0 +1,63 @@
+"use client";
+
+import { Flex } from "@styled-system/jsx";
+import type { ReactNode } from "react";
+import { useState } from "react";
+import { v4 as uuidv4 } from "uuid";
+
+import type { ToastProps } from ".";
+import Toast from ".";
+import { ToastContext } from "./ToastContext";
+
+interface ToastProviderProps {
+ children: ReactNode;
+ idPattern?: RegExp;
+}
+
+const ToastProvider = ({ children, idPattern }: ToastProviderProps) => {
+ const [toasts, setToasts] = useState([]);
+
+ const addToast = (
+ props: Omit & Partial>
+ ) => {
+ const newToast = {
+ ...props,
+ id: props.id || uuidv4(),
+ };
+
+ setToasts((prev) => [...prev, newToast]);
+ };
+
+ const removeToast = (id: string) => {
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
+ };
+
+ const filteredToasts = idPattern
+ ? toasts.filter((toast) => idPattern.test(toast.id))
+ : toasts;
+
+ return (
+
+ {toasts.length > 0 && (
+
+ {filteredToasts?.map((toast: ToastProps) => (
+
+ ))}
+
+ )}
+ {children}
+
+ );
+};
+
+export default ToastProvider;
diff --git a/packages/wow-ui/src/components/Toast/index.tsx b/packages/wow-ui/src/components/Toast/index.tsx
new file mode 100644
index 00000000..efd10216
--- /dev/null
+++ b/packages/wow-ui/src/components/Toast/index.tsx
@@ -0,0 +1,131 @@
+"use client";
+
+import { css } from "@styled-system/css";
+import type { FlexProps } from "@styled-system/jsx";
+import { Flex, styled } from "@styled-system/jsx";
+import type { CSSProperties } from "react";
+import { forwardRef, useEffect, useState } from "react";
+import type { IconProps } from "wowds-icons";
+import { Close, RightArrow, Warn } from "wowds-icons";
+
+import useToast from "./useToast";
+
+/**
+ * @description 토스트 컴포넌트입니다.
+ *
+ * @param {string} id - 토스트 컴포넌트의 id.
+ * @param {"default"|"close"|"arrow"} [type] - 토스트 컴포넌트의 타입.
+ * @param {string} text - 토스트 컴포넌트의 메인 텍스트.
+ * @param {ReactNode} icon - 토스트 컴포넌트의 좌측에 들어갈 아이콘.
+ * @param {()=>void} [onClickArrowIcon] - 화살표 아이콘을 클릭했을 때 호출되는 함수.
+ * @param {()=>void} [onRemove] - 토스트 컴포넌트가 닫히고 나서 호출되는 함수.
+ * @param {string} [subText] - 토스트 컴포넌트의 보조 텍스트.
+ * @param {string} [toastDuration] - 토스트 컴포넌트의 보여지는 시간.
+ * @param {CSSProperties} [style] - 커스텀 스타일을 적용하기 위한 객체.
+ * @param {string} [className] - 커스텀 클래스를 적용하기 위한 문자열.
+ */
+
+export interface ToastProps extends FlexProps {
+ id: string;
+ showLeftIcon?: boolean;
+ rightIcon?: "none" | "close" | "arrow";
+ text: string;
+ onClickArrowIcon?: () => void;
+ onRemove?: () => void;
+ subText?: string;
+ toastDuration?: number;
+ style?: CSSProperties;
+ className?: string;
+}
+
+const Toast = forwardRef(
+ ({
+ id,
+ text,
+ subText,
+ onClickArrowIcon,
+ onRemove,
+ rightIcon = "none",
+ showLeftIcon = false,
+ toastDuration,
+ ...rest
+ }: ToastProps) => {
+ const TOAST_DURATION = toastDuration || 2000;
+ const ANIMATION_DURATION = 200;
+ const { removeToast } = useToast();
+
+ const RightIcon = (props: IconProps) => {
+ if (rightIcon === "close")
+ return removeToast(id)} {...props} />;
+ else if (rightIcon === "arrow")
+ return ;
+ return null;
+ };
+
+ const [opacity, setOpacity] = useState(0.2);
+
+ useEffect(() => {
+ setOpacity(1);
+ const timeoutForRemove = setTimeout(() => {
+ removeToast(id);
+ onRemove?.();
+ }, TOAST_DURATION);
+
+ const timeoutForVisible = setTimeout(() => {
+ setOpacity(0);
+ }, TOAST_DURATION - ANIMATION_DURATION);
+
+ return () => {
+ clearTimeout(timeoutForRemove);
+ clearTimeout(timeoutForVisible);
+ };
+ }, [id, removeToast]);
+
+ return (
+
+
+ {showLeftIcon && }
+
+
+ {text}
+
+ {subText && (
+
+ {subText}
+
+ )}
+
+
+
+
+ );
+ }
+);
+
+const toastContainerStyle = css({
+ width: "22.375rem",
+ height: "fit-content",
+ padding: "0.75rem 1rem",
+
+ borderRadius: "md",
+
+ background: "backgroundDimmer",
+ backdropFilter: "blur(30px)",
+ boxShadow: "mono",
+});
+
+Toast.displayName = "Toast";
+export default Toast;
diff --git a/packages/wow-ui/src/components/Toast/useToast.ts b/packages/wow-ui/src/components/Toast/useToast.ts
new file mode 100644
index 00000000..31c618d7
--- /dev/null
+++ b/packages/wow-ui/src/components/Toast/useToast.ts
@@ -0,0 +1,12 @@
+import { useToastContext } from "./ToastContext";
+
+const useToast = () => {
+ const { addToast, removeToast } = useToastContext();
+
+ return {
+ toast: addToast,
+ removeToast,
+ };
+};
+
+export default useToast;
diff --git a/packages/wow-ui/src/hooks/useMergeRefs.ts b/packages/wow-ui/src/hooks/useMergeRefs.ts
new file mode 100644
index 00000000..11b535d8
--- /dev/null
+++ b/packages/wow-ui/src/hooks/useMergeRefs.ts
@@ -0,0 +1,13 @@
+import type { MutableRefObject, Ref } from "react";
+
+export function useMergeRefs(...refs: (Ref | null)[]) {
+ return (value: T | null) => {
+ refs.forEach((ref) => {
+ if (typeof ref === "function") {
+ ref(value);
+ } else if (ref !== null && typeof ref === "object") {
+ (ref as MutableRefObject).current = value;
+ }
+ });
+ };
+}
diff --git a/packages/wow-ui/src/types/DefaultProps.ts b/packages/wow-ui/src/types/DefaultProps.ts
new file mode 100644
index 00000000..ace10e37
--- /dev/null
+++ b/packages/wow-ui/src/types/DefaultProps.ts
@@ -0,0 +1,11 @@
+import type { CSSProperties } from "react";
+
+/**
+ * @description 컴포넌트에 전달한 기본적으로 전달한 props 입니다.
+ * @property {string} className - 컴포넌트에 전달할 커스텀 클래스명입니다.
+ * @property {CSSProperties} style - 컴포넌트에 전달할 커스텀 스타일입니다.
+ */
+export interface DefaultProps {
+ className?: string;
+ style?: CSSProperties;
+}
diff --git a/packages/wow-ui/src/types/index.ts b/packages/wow-ui/src/types/index.ts
index 2d701f74..e904c893 100644
--- a/packages/wow-ui/src/types/index.ts
+++ b/packages/wow-ui/src/types/index.ts
@@ -3,9 +3,9 @@ export type {
ButtonElementType,
MenuButtonProps,
ToggleButtonProps,
-} from "./Button";
+} from "./button.ts";
export type {
PolymorphicComponentProps,
PolymorphicComponentPropsWithRef,
PolymorphicRef,
-} from "./Polymorphic";
+} from "./polymorphic.ts";
diff --git a/packages/wow-ui/src/types/Polymorphic.ts b/packages/wow-ui/src/types/polymorphic.ts
similarity index 74%
rename from packages/wow-ui/src/types/Polymorphic.ts
rename to packages/wow-ui/src/types/polymorphic.ts
index 72c7681c..f9dedd89 100644
--- a/packages/wow-ui/src/types/Polymorphic.ts
+++ b/packages/wow-ui/src/types/polymorphic.ts
@@ -13,12 +13,12 @@ export type PolymorphicRef =
export type PolymorphicComponentPropsWithRef<
C extends ElementType,
- Props = {},
-> = Props & { ref?: PolymorphicRef };
+ ComponentProps = {},
+> = ComponentProps & { ref?: PolymorphicRef };
export type PolymorphicComponentProps<
T extends ElementType,
- Props = {},
+ ComponentProps = {},
> = AsProps &
ComponentPropsWithoutRef &
- PolymorphicComponentPropsWithRef;
+ PolymorphicComponentPropsWithRef;
diff --git a/packages/wow-ui/styled-system/styles.css b/packages/wow-ui/styled-system/styles.css
index 49dc1210..655517da 100644
--- a/packages/wow-ui/styled-system/styles.css
+++ b/packages/wow-ui/styled-system/styles.css
@@ -169,104 +169,443 @@ progress {
:-moz-focusring {
outline: auto;
}
-[hidden]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
+[hidden]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
display: none !important;
}
+body:not(#\#) {
+ div: border-box;
+ button: border-box;
+}
:where(:root, :host):not(#\#):not(#\#) {
+ --colors-red-50: #fdeceb;
+ --colors-red-100: #fbd9d7;
+ --colors-red-150: #f9c7c2;
+ --colors-red-200: #f7b4ae;
+ --colors-red-300: #f28e86;
--colors-red-400: #ee695d;
--colors-red-500: #ea4335;
+ --colors-red-600: #bb362a;
+ --colors-red-700: #8c2820;
+ --colors-red-800: #5e1b15;
+ --colors-red-850: #461410;
+ --colors-red-900: #2f0d0b;
+ --colors-red-950: #170705;
+ --colors-blue-50: #ebf4fe;
--colors-blue-100: #d7e9fd;
+ --colors-blue-150: #c3ddfd;
+ --colors-blue-200: #afd2fc;
+ --colors-blue-300: #86bcfa;
--colors-blue-400: #5ea5f9;
+ --colors-blue-500: #368ff7;
+ --colors-blue-600: #2b72c6;
+ --colors-blue-700: #205694;
+ --colors-blue-800: #163963;
+ --colors-blue-850: #102b4a;
+ --colors-blue-900: #0b1d31;
+ --colors-blue-950: #050e19;
+ --colors-yellow-50: #fef7e6;
+ --colors-yellow-100: #feeecc;
+ --colors-yellow-150: #fde6b3;
+ --colors-yellow-200: #fddd99;
+ --colors-yellow-300: #fbcd66;
+ --colors-yellow-400: #fabc33;
+ --colors-yellow-500: #f9ab00;
+ --colors-yellow-600: #c78900;
+ --colors-yellow-700: #956700;
+ --colors-yellow-800: #644400;
+ --colors-yellow-850: #4b3300;
+ --colors-yellow-900: #322200;
+ --colors-yellow-950: #191100;
+ --colors-green-50: #ebf6ee;
+ --colors-green-100: #d6eedd;
+ --colors-green-150: #c2e5cb;
+ --colors-green-200: #aedcba;
+ --colors-green-300: #85cb98;
+ --colors-green-400: #5db975;
+ --colors-green-500: #34a853;
+ --colors-green-600: #2a8642;
+ --colors-green-700: #1f6532;
+ --colors-green-800: #154321;
+ --colors-green-850: #103219;
+ --colors-green-900: #0a2211;
+ --colors-green-950: #051108;
+ --colors-mono-50: #f7f7f7;
+ --colors-mono-100: #f0f0f0;
+ --colors-mono-150: #e8e8e8;
+ --colors-mono-200: #e1e1e1;
+ --colors-mono-300: #d1d1d1;
+ --colors-mono-400: #c2c2c2;
+ --colors-mono-500: #b3b3b3;
+ --colors-mono-600: #8f8f8f;
+ --colors-mono-700: #6b6b6b;
+ --colors-mono-800: #484848;
+ --colors-mono-900: #242424;
+ --colors-mono-950: #121212;
+ --colors-white: #ffffff;
+ --colors-black: #000000;
--colors-primary: #368ff7;
+ --colors-success: #2a8642;
+ --colors-error: #bb362a;
+ --colors-background-normal: #ffffff;
+ --colors-background-alternative: #f7f7f7;
+ --colors-background-dimmer: rgba(0, 0, 0, 0.8);
+ --colors-sub: #6b6b6b;
+ --colors-outline: #c2c2c2;
+ --colors-text-black: #121212;
+ --colors-text-white: #ffffff;
--colors-dark-disabled: #c2c2c2;
+ --colors-light-disabled: #e1e1e1;
+ --colors-blue-hover: #2b72c6;
+ --colors-mono-hover: #121212;
+ --colors-elevated-hover: rgba(16, 43, 74, 0.2);
--colors-blue-pressed: #5ea5f9;
--colors-blue-background-pressed: #ebf4fe;
+ --colors-mono-background-pressed: #f7f7f7;
+ --colors-shadow-small: rgba(0, 0, 0, 0.1);
+ --colors-shadow-medium: rgba(0, 0, 0, 0.2);
+ --colors-blue-shadow: rgba(16, 43, 74, 0.2);
+ --colors-discord: #5566fb;
+ --colors-github: #000000;
+ --colors-secondary-yellow: #f9ab00;
+ --colors-secondary-green: #34a853;
+ --colors-secondary-red: #ea4335;
+ --colors-error-background: #fbd9d7;
+ --colors-blue-disabled: #d7e9fd;
+ --colors-text-blue-disabled: #afd2fc;
+}
+.text_blue\.50:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-50);
+}
+.text_blue\.100:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-100);
+}
+.text_blue\.150:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-150);
+}
+.text_blue\.200:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-200);
+}
+.text_blue\.300:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-300);
+}
+.text_blue\.400:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-400);
+}
+.text_blue\.500:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-500);
+}
+.text_blue\.600:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-600);
+}
+.text_blue\.700:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-700);
+}
+.text_blue\.800:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-800);
+}
+.text_blue\.850:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-850);
+}
+.text_blue\.900:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-900);
+}
+.text_blue\.950:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-950);
+}
+.text_yellow\.50:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-50);
+}
+.text_yellow\.100:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-100);
+}
+.text_yellow\.150:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-150);
+}
+.text_yellow\.200:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-200);
+}
+.text_yellow\.300:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-300);
+}
+.text_yellow\.400:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-400);
+}
+.text_yellow\.500:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-500);
+}
+.text_yellow\.600:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-600);
+}
+.text_yellow\.700:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-700);
+}
+.text_yellow\.800:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-800);
+}
+.text_yellow\.850:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-850);
+}
+.text_yellow\.900:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-900);
+}
+.text_yellow\.950:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-yellow-950);
+}
+.text_green\.50:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-50);
+}
+.text_green\.100:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-100);
+}
+.text_green\.150:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-150);
+}
+.text_green\.200:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-200);
+}
+.text_green\.300:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-300);
+}
+.text_green\.400:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-400);
+}
+.text_green\.500:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-500);
+}
+.text_green\.600:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-600);
+}
+.text_green\.700:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-700);
+}
+.text_green\.800:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-800);
+}
+.text_green\.850:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-850);
+}
+.text_green\.900:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-900);
+}
+.text_green\.950:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-green-950);
+}
+.text_red\.50:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-50);
+}
+.text_red\.100:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-100);
+}
+.text_red\.150:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-150);
+}
+.text_red\.200:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-200);
+}
+.text_red\.300:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-300);
+}
+.text_red\.400:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-400);
+}
+.text_red\.500:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-500);
+}
+.text_red\.600:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-600);
+}
+.text_red\.700:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-700);
+}
+.text_red\.800:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-800);
+}
+.text_red\.850:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-850);
+}
+.text_red\.900:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-900);
+}
+.text_red\.950:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-red-950);
+}
+.text_mono\.50:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-50);
+}
+.text_mono\.100:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-100);
+}
+.text_mono\.150:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-150);
+}
+.text_mono\.200:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-200);
+}
+.text_mono\.300:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-300);
+}
+.text_mono\.400:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-400);
+}
+.text_mono\.500:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-500);
+}
+.text_mono\.600:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-600);
+}
+.text_mono\.700:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-700);
+}
+.text_mono\.800:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-800);
+}
+.text_mono\.850:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: mono.850;
+}
+.text_mono\.900:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-900);
+}
+.text_mono\.950:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-950);
+}
+.text_white:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-white);
+}
+.text_black:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-black);
+}
+.text_whiteOpacity\.20:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: whiteOpacity.20;
+}
+.text_whiteOpacity\.40:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: whiteOpacity.40;
+}
+.text_whiteOpacity\.60:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: whiteOpacity.60;
+}
+.text_whiteOpacity\.80:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: whiteOpacity.80;
+}
+.text_blackOpacity\.20:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: blackOpacity.20;
+}
+.text_blackOpacity\.40:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: blackOpacity.40;
+}
+.text_blackOpacity\.60:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: blackOpacity.60;
+}
+.text_blackOpacity\.80:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: blackOpacity.80;
+}
+.text_primary:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-primary);
+}
+.text_success:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-success);
+}
+.text_error:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-error);
+}
+.text_backgroundNormal:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-background-normal);
+}
+.text_backgroundAlternative:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-background-alternative);
+}
+.text_backgroundDimmer:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-background-dimmer);
+}
+.text_errorBackground:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-error-background);
+}
+.text_sub:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-sub);
+}
+.text_outline:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-outline);
}
-.textStyle_body2:not(#\#):not(#\#):not(#\#):not(#\#) {
- letter-spacing: -0.00875rem;
- font-size: 0.875rem;
- line-height: 160%;
- font-weight: 500;
+.text_textBlack:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-text-black);
}
-.bg_blue\.100:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- background: var(--colors-blue-100);
+.text_textWhite:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-text-white);
}
-.px_4:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- padding-inline: 4px;
+.text_darkDisabled:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-dark-disabled);
}
-.py_3:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- padding-block: 3px;
+.text_lightDisabled:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-light-disabled);
}
-.rounded_md:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- border-radius: md;
+.text_blueDisabled:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-disabled);
}
-.bg_red\.400:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- background: var(--colors-red-400);
+.text_textBlueDisabled:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-text-blue-disabled);
}
-.w_20:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- width: 20px;
+.text_blueHover:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-hover);
}
-.h_20:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- height: 20px;
+.text_monoHover:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-hover);
}
-.rounded_9999:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- border-radius: 9999px;
+.text_elevatedHover:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-elevated-hover);
}
-.d_flex:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- display: flex;
+.text_bluePressed:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-pressed);
}
-.bg_blueBackgroundPressed:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- background: var(--colors-blue-background-pressed);
+.text_blueBackgroundPressed:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-background-pressed);
}
-.w_10:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- width: 10px;
+.text_monoBackgroundPressed:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-mono-background-pressed);
}
-.h_10:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- height: 10px;
+.text_shadowSmall:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-shadow-small);
}
-.bg_primary:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- background: var(--colors-primary);
+.text_shadowMedium:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-shadow-medium);
}
-.gap_8:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- gap: 8px;
+.text_blueShadow:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-blue-shadow);
}
-.d_none:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- display: none;
+.text_discord:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-discord);
}
-.gap_0\.5rem:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- gap: 0.5rem;
+.text_github:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-github);
}
-.font_Inter:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- font-family: Inter;
+.text_secondaryYellow:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-secondary-yellow);
}
-.border-w_1:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- border-width: 1px;
+.text_secondaryGreen:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-secondary-green);
}
-.items_center:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- align-items: center;
+.text_secondaryRed:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: var(--colors-secondary-red);
}
-.justify_center:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- justify-content: center;
+.text_blueGradientDark:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: blueGradientDark;
}
-.border_primary:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- border-color: var(--colors-primary);
+.text_blueGradientLight:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: blueGradientLight;
}
-.border_darkDisabled:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- border-color: var(--colors-dark-disabled);
+.text_redGradientDark:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: redGradientDark;
}
-.border_bluePressed:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- border-color: var(--colors-blue-pressed);
+.text_redGradientLight:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: redGradientLight;
}
-.flex_column:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#) {
- flex-direction: column;
+.text_greenGradientDark:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: greenGradientDark;
}
-.hover\:bg_blue\.400:is(:hover, [data-hover]):not(#\#):not(#\#):not(#\#):not(
- #\#
- ):not(#\#) {
- background: var(--colors-blue-400);
+.text_greenGradientLight:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: greenGradientLight;
}
-.hover\:bg_red\.500:is(:hover, [data-hover]):not(#\#):not(#\#):not(#\#):not(
- #\#
- ):not(#\#) {
- background: var(--colors-red-500);
+.text_yellowGradientDark:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: yellowGradientDark;
}
+.text_yellowGradientLight:not(#\#):not(#\#):not(#\#):not(#\#) {
+ color: yellowGradientLight;
+}
\ No newline at end of file
diff --git a/packages/wow-ui/styled-system/tokens/index.js b/packages/wow-ui/styled-system/tokens/index.js
index 930662c7..a6ec6779 100644
--- a/packages/wow-ui/styled-system/tokens/index.js
+++ b/packages/wow-ui/styled-system/tokens/index.js
@@ -343,6 +343,10 @@ const tokens = {
value: 10,
variable: "var(--z-index-dropdown)",
},
+ "zIndex.overlay": {
+ value: 9999,
+ variable: "var(--z-index-overlay)",
+ },
"shadows.blue": {
value: "0px 4px 8px 0px rgba(16, 43, 74, 0.2)",
variable: "var(--shadows-blue)",
diff --git a/packages/wow-ui/styled-system/tokens/tokens.d.ts b/packages/wow-ui/styled-system/tokens/tokens.d.ts
index 24b9c593..45319136 100644
--- a/packages/wow-ui/styled-system/tokens/tokens.d.ts
+++ b/packages/wow-ui/styled-system/tokens/tokens.d.ts
@@ -86,6 +86,7 @@ export type Token =
| "borderWidths.button"
| "borderWidths.arrow"
| "zIndex.dropdown"
+ | "zIndex.overlay"
| "shadows.blue"
| "shadows.mono"
| "breakpoints.xs"
@@ -323,7 +324,7 @@ export type RadiusToken = "sm" | "md" | "full";
export type BorderWidthToken = "button" | "arrow";
-export type ZIndexToken = "dropdown";
+export type ZIndexToken = "dropdown" | "overlay";
export type ShadowToken = "blue" | "mono";
diff --git a/packages/wow-ui/styled-system/types/prop-type.d.ts b/packages/wow-ui/styled-system/types/prop-type.d.ts
index 195159ce..bfcf5a77 100644
--- a/packages/wow-ui/styled-system/types/prop-type.d.ts
+++ b/packages/wow-ui/styled-system/types/prop-type.d.ts
@@ -679,7 +679,9 @@ export interface UtilityValues {
| "body3"
| "label1"
| "label2"
- | "label3";
+ | "label3"
+ | "header1"
+ | "header2";
}
type WithColorOpacityModifier = T extends string ? `${T}/${string}` : T;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fd92978b..f80dc987 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -268,6 +268,9 @@ importers:
react-day-picker:
specifier: ^9.0.8
version: 9.0.8(react@18.2.0)
+ uuid:
+ specifier: ^10.0.0
+ version: 10.0.0
wowds-icons:
specifier: workspace:^
version: link:../wow-icons
@@ -323,6 +326,9 @@ importers:
'@types/react-dom':
specifier: ^18.2.19
version: 18.2.19
+ '@types/uuid':
+ specifier: ^10.0.0
+ version: 10.0.0
axe-playwright:
specifier: ^2.0.1
version: 2.0.1(playwright@1.45.0)
@@ -5448,6 +5454,10 @@ packages:
resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
dev: true
+ /@types/uuid@10.0.0:
+ resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
+ dev: true
+
/@types/uuid@9.0.8:
resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
dev: true
@@ -15661,6 +15671,11 @@ packages:
engines: {node: '>= 0.4.0'}
dev: true
+ /uuid@10.0.0:
+ resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
+ hasBin: true
+ dev: false
+
/uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true