add Button component and Storybook configuration
Some checks failed
CI TEST (Vault secrets) / build (push) Failing after 9s
Some checks failed
CI TEST (Vault secrets) / build (push) Failing after 9s
This commit is contained in:
30
.storybook/constants.ts
Normal file
30
.storybook/constants.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export const CUSTOM_VIEW_PORTS = {
|
||||
sm: {
|
||||
name: "Small",
|
||||
styles: {
|
||||
width: "375px",
|
||||
height: "800px",
|
||||
},
|
||||
},
|
||||
md: {
|
||||
name: "Medium",
|
||||
styles: {
|
||||
width: "768px",
|
||||
height: "1024px",
|
||||
},
|
||||
},
|
||||
lg: {
|
||||
name: "Large",
|
||||
styles: {
|
||||
width: "1024px",
|
||||
height: "1366px",
|
||||
},
|
||||
},
|
||||
xl: {
|
||||
name: "Extra Large",
|
||||
styles: {
|
||||
width: "1440px",
|
||||
height: "900px",
|
||||
},
|
||||
},
|
||||
};
|
||||
18
.storybook/main.js
Normal file
18
.storybook/main.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
export default {
|
||||
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||
staticDirs: ["../public"],
|
||||
framework: {
|
||||
name: "@storybook-astro/framework",
|
||||
},
|
||||
core: {
|
||||
disableTelemetry: true,
|
||||
enableCrashReports: false,
|
||||
},
|
||||
async viteFinal(config) {
|
||||
config.plugins = config.plugins || [];
|
||||
config.plugins.push(tailwindcss());
|
||||
return config;
|
||||
},
|
||||
};
|
||||
1
.storybook/preview.css
Normal file
1
.storybook/preview.css
Normal file
@@ -0,0 +1 @@
|
||||
@import "../src/app/styles/global.css";
|
||||
22
.storybook/preview.js
Normal file
22
.storybook/preview.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import "./preview.css";
|
||||
import { INITIAL_VIEWPORTS } from "storybook/viewport";
|
||||
import { CUSTOM_VIEW_PORTS } from "./constants.ts";
|
||||
|
||||
const preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
backgrounds: {
|
||||
default: "darky",
|
||||
values: [{ name: "darky", value: "#0b121a" }],
|
||||
},
|
||||
viewport: {
|
||||
options: { ...CUSTOM_VIEW_PORTS, ...INITIAL_VIEWPORTS },
|
||||
},
|
||||
},
|
||||
};
|
||||
export default preview;
|
||||
@@ -19,4 +19,5 @@ export default defineConfig({
|
||||
alias,
|
||||
},
|
||||
},
|
||||
server: {},
|
||||
});
|
||||
|
||||
13
package.json
13
package.json
@@ -16,7 +16,9 @@
|
||||
"lint": "astro check && biome check .",
|
||||
"format": "biome format . --write",
|
||||
"prepare": "husky",
|
||||
"ci": "astro check && biome ci ."
|
||||
"ci": "astro check && biome ci .",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,json,jsonc,css,scss}": [
|
||||
@@ -29,14 +31,17 @@
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"@webtui/css": "^0.1.7",
|
||||
"astro": "^6.1.8",
|
||||
"astro": "^6.2.1",
|
||||
"tailwindcss": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.9",
|
||||
"@biomejs/biome": "2.4.13",
|
||||
"@storybook-astro/framework": "^1.1.0",
|
||||
"@storybook/builder-vite": "^10.3.6",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.4.0",
|
||||
"typescript": "^6.0.3",
|
||||
"@astrojs/check": "^0.9.9"
|
||||
"storybook": "^10.3.6",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
3436
pnpm-lock.yaml
generated
3436
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1
src/shared/ui/Button/index.ts
Normal file
1
src/shared/ui/Button/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./ui/Button.astro";
|
||||
118
src/shared/ui/Button/ui/Button.astro
Normal file
118
src/shared/ui/Button/ui/Button.astro
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
|
||||
type Variant = "primary" | "secondary" | "ghost" | "danger";
|
||||
type Size = "sm" | "md" | "lg";
|
||||
|
||||
type CommonProps = {
|
||||
variant?: Variant;
|
||||
size?: Size;
|
||||
fullWidth?: boolean;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
class?: string;
|
||||
};
|
||||
|
||||
type ButtonAsButtonProps = CommonProps &
|
||||
Omit<HTMLAttributes<"button">, "class" | "disabled"> & {
|
||||
as?: "button";
|
||||
type?: "button" | "submit" | "reset";
|
||||
href?: never;
|
||||
};
|
||||
|
||||
type ButtonAsLinkProps = CommonProps &
|
||||
Omit<HTMLAttributes<"a">, "class" | "href"> & {
|
||||
as: "a";
|
||||
href: string;
|
||||
type?: never;
|
||||
};
|
||||
|
||||
type Props = ButtonAsButtonProps | ButtonAsLinkProps;
|
||||
|
||||
const {
|
||||
as = "button",
|
||||
variant = "primary",
|
||||
size = "md",
|
||||
fullWidth = false,
|
||||
loading = false,
|
||||
disabled = false,
|
||||
class: className = "",
|
||||
...attrs
|
||||
} = Astro.props as Props;
|
||||
|
||||
const isLink = as === "a";
|
||||
const isDisabled = disabled || loading;
|
||||
|
||||
const baseClass =
|
||||
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md border font-medium transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400/60 focus-visible:ring-offset-2 focus-visible:ring-offset-black disabled:pointer-events-none disabled:opacity-50";
|
||||
|
||||
const variantClassMap: Record<Variant, string> = {
|
||||
primary:
|
||||
"border-cyan-400 bg-cyan-400 text-black hover:border-cyan-300 hover:bg-cyan-300",
|
||||
secondary:
|
||||
"border-zinc-700 bg-zinc-900 text-white hover:border-zinc-600 hover:bg-zinc-800",
|
||||
ghost:
|
||||
"border-transparent bg-transparent text-zinc-200 hover:bg-white/5 hover:text-white",
|
||||
danger:
|
||||
"border-red-500 bg-red-500 text-white hover:border-red-400 hover:bg-red-400",
|
||||
};
|
||||
|
||||
const sizeClassMap: Record<Size, string> = {
|
||||
sm: "h-9 px-3 text-sm",
|
||||
md: "h-10 px-4 text-sm",
|
||||
lg: "h-11 px-5 text-base",
|
||||
};
|
||||
|
||||
const widthClass = fullWidth ? "w-full" : "";
|
||||
const loadingClass = loading ? "cursor-wait" : "";
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
variantClassMap[variant],
|
||||
sizeClassMap[size],
|
||||
widthClass,
|
||||
loadingClass,
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
---
|
||||
|
||||
{
|
||||
isLink ? (
|
||||
<a
|
||||
{...(attrs as Omit<ButtonAsLinkProps, keyof CommonProps | "as" | "href" | "type">)}
|
||||
href={(Astro.props as ButtonAsLinkProps).href}
|
||||
class={classes}
|
||||
aria-disabled={isDisabled ? "true" : undefined}
|
||||
data-loading={loading ? "true" : undefined}
|
||||
tabindex={isDisabled ? -1 : undefined}
|
||||
>
|
||||
{loading && (
|
||||
<span aria-hidden="true" class="inline-block size-4 animate-spin rounded-full border-2 border-current border-r-transparent" />
|
||||
)}
|
||||
<slot name="leftIcon" />
|
||||
<span class:list={["inline-flex items-center", loading && "opacity-80"]}>
|
||||
<slot />
|
||||
</span>
|
||||
<slot name="rightIcon" />
|
||||
</a>
|
||||
) : (
|
||||
<button
|
||||
{...(attrs as Omit<ButtonAsButtonProps, keyof CommonProps | "as" | "href" | "type">)}
|
||||
type={(Astro.props as ButtonAsButtonProps).type ?? "button"}
|
||||
class={classes}
|
||||
disabled={isDisabled}
|
||||
data-loading={loading ? "true" : undefined}
|
||||
>
|
||||
{loading && (
|
||||
<span aria-hidden="true" class="inline-block size-4 animate-spin rounded-full border-2 border-current border-r-transparent" />
|
||||
)}
|
||||
<slot name="leftIcon" />
|
||||
<span class:list={["inline-flex items-center", loading && "opacity-80"]}>
|
||||
<slot />
|
||||
</span>
|
||||
<slot name="rightIcon" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
116
src/shared/ui/Button/ui/Button.stories.tsx
Normal file
116
src/shared/ui/Button/ui/Button.stories.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import Button from "./Button.astro";
|
||||
|
||||
export default {
|
||||
title: "shared/ui/Button",
|
||||
component: Button,
|
||||
args: {
|
||||
children: "Button",
|
||||
variant: "primary",
|
||||
size: "md",
|
||||
fullWidth: false,
|
||||
loading: false,
|
||||
disabled: false,
|
||||
},
|
||||
argTypes: {
|
||||
as: {
|
||||
control: "radio",
|
||||
options: ["button", "a"],
|
||||
},
|
||||
variant: {
|
||||
control: "select",
|
||||
options: ["primary", "secondary", "ghost", "danger"],
|
||||
},
|
||||
size: {
|
||||
control: "select",
|
||||
options: ["sm", "md", "lg"],
|
||||
},
|
||||
fullWidth: {
|
||||
control: "boolean",
|
||||
},
|
||||
loading: {
|
||||
control: "boolean",
|
||||
},
|
||||
disabled: {
|
||||
control: "boolean",
|
||||
},
|
||||
type: {
|
||||
control: "select",
|
||||
options: ["button", "submit", "reset"],
|
||||
},
|
||||
href: {
|
||||
control: "text",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Primary = {
|
||||
args: {
|
||||
children: "Primary button",
|
||||
variant: "primary",
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary = {
|
||||
args: {
|
||||
children: "Secondary button",
|
||||
variant: "secondary",
|
||||
},
|
||||
};
|
||||
|
||||
export const Ghost = {
|
||||
args: {
|
||||
children: "Ghost button",
|
||||
variant: "ghost",
|
||||
},
|
||||
};
|
||||
|
||||
export const Danger = {
|
||||
args: {
|
||||
children: "Delete",
|
||||
variant: "danger",
|
||||
},
|
||||
};
|
||||
|
||||
export const Loading = {
|
||||
args: {
|
||||
children: "Loading...",
|
||||
loading: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled = {
|
||||
args: {
|
||||
children: "Disabled",
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const AsLink = {
|
||||
args: {
|
||||
as: "a",
|
||||
href: "/demo",
|
||||
children: "Open demo",
|
||||
variant: "ghost",
|
||||
},
|
||||
};
|
||||
|
||||
export const FullWidth = {
|
||||
args: {
|
||||
children: "Continue",
|
||||
fullWidth: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Small = {
|
||||
args: {
|
||||
children: "Small",
|
||||
size: "sm",
|
||||
},
|
||||
};
|
||||
|
||||
export const Large = {
|
||||
args: {
|
||||
children: "Large",
|
||||
size: "lg",
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user