@bbki.ng/ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.ts +21 -0
- package/.storybook/preview-head.html +10 -0
- package/.storybook/preview.tsx +30 -0
- package/CHANGELOG.md +8 -0
- package/README.md +124 -0
- package/package.json +57 -0
- package/scripts/build-tokens.ts +170 -0
- package/src/atoms/blink-dot/BlinkDot.stories.tsx +44 -0
- package/src/atoms/blink-dot/BlinkDot.tsx +45 -0
- package/src/atoms/blink-dot/index.ts +2 -0
- package/src/atoms/button/Button.stories.tsx +84 -0
- package/src/atoms/button/Button.tsx +59 -0
- package/src/atoms/button/Button.types.ts +20 -0
- package/src/atoms/button/Button.variants.ts +58 -0
- package/src/atoms/button/index.ts +3 -0
- package/src/atoms/link/Link.stories.tsx +121 -0
- package/src/atoms/link/Link.tsx +69 -0
- package/src/atoms/link/Link.types.ts +26 -0
- package/src/atoms/link/Link.variants.ts +55 -0
- package/src/atoms/link/index.ts +3 -0
- package/src/atoms/logo/Logo.stories.tsx +37 -0
- package/src/atoms/logo/Logo.tsx +49 -0
- package/src/atoms/logo/Logo.types.ts +4 -0
- package/src/atoms/logo/index.ts +2 -0
- package/src/index.ts +54 -0
- package/src/layout/container/Container.stories.tsx +73 -0
- package/src/layout/container/Container.tsx +57 -0
- package/src/layout/container/index.ts +2 -0
- package/src/layout/grid/Grid.stories.tsx +106 -0
- package/src/layout/grid/Grid.tsx +86 -0
- package/src/layout/grid/index.ts +2 -0
- package/src/layout/index.ts +4 -0
- package/src/molecules/article/Article.stories.tsx +45 -0
- package/src/molecules/article/Article.tsx +25 -0
- package/src/molecules/article/Article.types.ts +11 -0
- package/src/molecules/article/index.ts +2 -0
- package/src/molecules/breadcrumb/Breadcrumb.stories.tsx +60 -0
- package/src/molecules/breadcrumb/Breadcrumb.tsx +43 -0
- package/src/molecules/breadcrumb/Breadcrumb.types.ts +19 -0
- package/src/molecules/breadcrumb/index.ts +2 -0
- package/src/molecules/list/List.stories.tsx +84 -0
- package/src/molecules/list/List.tsx +79 -0
- package/src/molecules/list/List.types.ts +23 -0
- package/src/molecules/list/index.ts +2 -0
- package/src/molecules/nav/Nav.stories.tsx +45 -0
- package/src/molecules/nav/Nav.tsx +29 -0
- package/src/molecules/nav/Nav.types.ts +10 -0
- package/src/molecules/nav/index.ts +2 -0
- package/src/molecules/panel/Panel.stories.tsx +42 -0
- package/src/molecules/panel/Panel.tsx +27 -0
- package/src/molecules/panel/Panel.types.ts +6 -0
- package/src/molecules/panel/index.ts +2 -0
- package/src/molecules/table/Table.stories.tsx +54 -0
- package/src/molecules/table/Table.tsx +31 -0
- package/src/molecules/table/Table.types.ts +20 -0
- package/src/molecules/table/index.ts +2 -0
- package/src/organisms/canvas/Canvas.tsx +79 -0
- package/src/organisms/canvas/Canvas.types.ts +25 -0
- package/src/organisms/canvas/index.ts +3 -0
- package/src/organisms/canvas/useRenderer.ts +44 -0
- package/src/organisms/drop-image/DropImage.stories.tsx +36 -0
- package/src/organisms/drop-image/DropImage.tsx +193 -0
- package/src/organisms/drop-image/DropImage.types.ts +26 -0
- package/src/organisms/drop-image/index.ts +3 -0
- package/src/organisms/drop-image/useDropImage.ts +124 -0
- package/src/organisms/drop-image/utils.ts +1 -0
- package/src/organisms/drop-zone/DropZone.tsx +58 -0
- package/src/organisms/drop-zone/DropZone.types.ts +9 -0
- package/src/organisms/drop-zone/index.ts +2 -0
- package/src/organisms/loading-spiral/LoadingSpiral.stories.tsx +30 -0
- package/src/organisms/loading-spiral/LoadingSpiral.tsx +44 -0
- package/src/organisms/loading-spiral/LoadingSpiral.types.ts +4 -0
- package/src/organisms/loading-spiral/constants.ts +62 -0
- package/src/organisms/loading-spiral/createOptions.ts +31 -0
- package/src/organisms/loading-spiral/createSettings.ts +26 -0
- package/src/organisms/loading-spiral/index.ts +2 -0
- package/src/organisms/loading-spiral/useCanvasRef.ts +23 -0
- package/src/organisms/loading-spiral/utils.ts +5 -0
- package/src/organisms/page/Page.stories.tsx +65 -0
- package/src/organisms/page/Page.tsx +71 -0
- package/src/organisms/page/Page.types.ts +23 -0
- package/src/organisms/page/index.ts +8 -0
- package/src/styles.css +151 -0
- package/src/theme/ThemeContext.tsx +20 -0
- package/src/theme/ThemeProvider.tsx +93 -0
- package/src/theme/index.ts +3 -0
- package/src/tokens/css/dark.css +111 -0
- package/src/tokens/css/light.css +111 -0
- package/src/tokens/index.ts +127 -0
- package/tokens/base/colors.json +54 -0
- package/tokens/base/shadows.json +34 -0
- package/tokens/base/spacing.json +21 -0
- package/tokens/base/typography.json +35 -0
- package/tokens/semantic/dark.json +50 -0
- package/tokens/semantic/light.json +54 -0
- package/tsconfig.json +22 -0
- package/vite.config.ts +44 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Button 变体定义
|
|
5
|
+
*/
|
|
6
|
+
export const buttonVariants = cva(
|
|
7
|
+
[
|
|
8
|
+
'inline-flex items-center justify-center',
|
|
9
|
+
'font-sans font-medium',
|
|
10
|
+
'transition-all duration-300 ease-in-out',
|
|
11
|
+
'active:not([disabled]):scale-[0.95]',
|
|
12
|
+
],
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
default: [
|
|
17
|
+
'text-content-primary',
|
|
18
|
+
'bg-transparent',
|
|
19
|
+
'shadow-[var(--shadow-button-hover)]',
|
|
20
|
+
'hover:shadow-[var(--shadow-button)]',
|
|
21
|
+
'active:shadow-[var(--shadow-none)]',
|
|
22
|
+
],
|
|
23
|
+
primary: [
|
|
24
|
+
'text-content-action',
|
|
25
|
+
'bg-transparent',
|
|
26
|
+
'shadow-[var(--shadow-button-hover)]',
|
|
27
|
+
'hover:shadow-[var(--shadow-button)]',
|
|
28
|
+
'active:shadow-[var(--shadow-none)]',
|
|
29
|
+
],
|
|
30
|
+
danger: [
|
|
31
|
+
'text-content-danger',
|
|
32
|
+
'bg-transparent',
|
|
33
|
+
'shadow-[var(--shadow-button-hover)]',
|
|
34
|
+
'hover:shadow-[var(--shadow-button)]',
|
|
35
|
+
'active:shadow-[var(--shadow-none)]',
|
|
36
|
+
],
|
|
37
|
+
ghost: ['text-content-primary', 'bg-transparent', 'shadow-none'],
|
|
38
|
+
disabled: ['text-content-disabled', 'bg-transparent', 'cursor-not-allowed', 'shadow-none'],
|
|
39
|
+
},
|
|
40
|
+
size: {
|
|
41
|
+
sm: 'py-4 px-4',
|
|
42
|
+
md: 'py-4 px-8',
|
|
43
|
+
lg: 'py-4 px-8',
|
|
44
|
+
},
|
|
45
|
+
transparent: {
|
|
46
|
+
true: 'shadow-none pointer-events-none',
|
|
47
|
+
false: '',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
defaultVariants: {
|
|
51
|
+
variant: 'default',
|
|
52
|
+
size: 'md',
|
|
53
|
+
transparent: false,
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
import { Link } from './Link';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Link> = {
|
|
6
|
+
title: 'Atoms/Link',
|
|
7
|
+
component: Link,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'centered',
|
|
10
|
+
},
|
|
11
|
+
tags: ['autodocs'],
|
|
12
|
+
argTypes: {
|
|
13
|
+
variant: {
|
|
14
|
+
control: 'select',
|
|
15
|
+
options: ['default', 'danger', 'muted'],
|
|
16
|
+
description: '链接样式变体',
|
|
17
|
+
},
|
|
18
|
+
external: {
|
|
19
|
+
control: 'boolean',
|
|
20
|
+
description: '是否为外部链接',
|
|
21
|
+
},
|
|
22
|
+
readonly: {
|
|
23
|
+
control: 'boolean',
|
|
24
|
+
description: '只读模式',
|
|
25
|
+
},
|
|
26
|
+
status: {
|
|
27
|
+
control: 'select',
|
|
28
|
+
options: ['blink', 'still', 'hidden'],
|
|
29
|
+
description: 'BlinkDot 状态指示器',
|
|
30
|
+
},
|
|
31
|
+
to: {
|
|
32
|
+
control: 'text',
|
|
33
|
+
description: '链接地址',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
decorators: [
|
|
37
|
+
Story => (
|
|
38
|
+
<MemoryRouter>
|
|
39
|
+
<Story />
|
|
40
|
+
</MemoryRouter>
|
|
41
|
+
),
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default meta;
|
|
46
|
+
type Story = StoryObj<typeof meta>;
|
|
47
|
+
|
|
48
|
+
export const Default: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
children: 'Default Link',
|
|
51
|
+
to: '/foo/bar',
|
|
52
|
+
variant: 'default',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const Variants: Story = {
|
|
57
|
+
render: () => (
|
|
58
|
+
<div className="flex gap-6">
|
|
59
|
+
<Link to="/" variant="default">
|
|
60
|
+
Default (Action)
|
|
61
|
+
</Link>
|
|
62
|
+
<Link to="/" variant="danger">
|
|
63
|
+
Danger
|
|
64
|
+
</Link>
|
|
65
|
+
<Link to="/" variant="muted">
|
|
66
|
+
Muted
|
|
67
|
+
</Link>
|
|
68
|
+
<Link to="/" variant="special">
|
|
69
|
+
Special
|
|
70
|
+
</Link>
|
|
71
|
+
</div>
|
|
72
|
+
),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const External: Story = {
|
|
76
|
+
args: {
|
|
77
|
+
children: 'External Link',
|
|
78
|
+
to: 'https://bbki.ng',
|
|
79
|
+
external: true,
|
|
80
|
+
variant: 'default',
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const Readonly: Story = {
|
|
85
|
+
args: {
|
|
86
|
+
children: 'Read Only Text',
|
|
87
|
+
to: '/',
|
|
88
|
+
readonly: true,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const WithStatus: Story = {
|
|
93
|
+
render: () => (
|
|
94
|
+
<div className="flex gap-6">
|
|
95
|
+
<Link to="/" status="blink">
|
|
96
|
+
Blinking Status
|
|
97
|
+
</Link>
|
|
98
|
+
<Link to="/" status="still">
|
|
99
|
+
Still Status
|
|
100
|
+
</Link>
|
|
101
|
+
<Link to="/" status="hidden">
|
|
102
|
+
Hidden Status
|
|
103
|
+
</Link>
|
|
104
|
+
</div>
|
|
105
|
+
),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const SpecialContent: Story = {
|
|
109
|
+
args: {
|
|
110
|
+
children: '小乌鸦',
|
|
111
|
+
to: '/',
|
|
112
|
+
variant: 'special',
|
|
113
|
+
},
|
|
114
|
+
parameters: {
|
|
115
|
+
docs: {
|
|
116
|
+
description: {
|
|
117
|
+
story: '当链接内容包含 "小乌鸦" 时,自动使用 danger 样式',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Link as BaseLink } from 'react-router-dom';
|
|
3
|
+
import { twMerge } from 'tailwind-merge';
|
|
4
|
+
import { linkVariants } from './Link.variants';
|
|
5
|
+
import { LinkProps } from './Link.types';
|
|
6
|
+
import { BlinkDot } from '../blink-dot';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Link 组件
|
|
10
|
+
*
|
|
11
|
+
* 支持内部路由链接、外部链接和只读模式。
|
|
12
|
+
* 使用语义化颜色 token,自动适配 light/dark 主题。
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <Link to="/about">About Page</Link>
|
|
16
|
+
* <Link to="https://example.com" external>External Link</Link>
|
|
17
|
+
* <Link to="/" readonly>Read Only</Link>
|
|
18
|
+
*/
|
|
19
|
+
export const Link: React.FC<LinkProps> = ({
|
|
20
|
+
to,
|
|
21
|
+
variant = 'default',
|
|
22
|
+
external,
|
|
23
|
+
className,
|
|
24
|
+
children,
|
|
25
|
+
status,
|
|
26
|
+
readonly,
|
|
27
|
+
...rest
|
|
28
|
+
}) => {
|
|
29
|
+
// 特殊逻辑:如果内容包含 "小乌鸦",使用 danger 样式
|
|
30
|
+
const isSpecialContent = typeof children === 'string' && children.includes('小乌鸦');
|
|
31
|
+
const effectiveVariant = isSpecialContent ? 'special' : variant;
|
|
32
|
+
|
|
33
|
+
const linkCls = twMerge(linkVariants({ variant: effectiveVariant }), className);
|
|
34
|
+
|
|
35
|
+
// 只读模式:显示为静态文本
|
|
36
|
+
if (readonly) {
|
|
37
|
+
const isNonEnName = typeof children === 'string' && !/^[a-zA-Z~]+$/.test(children);
|
|
38
|
+
const offsetCls = isNonEnName ? 'relative top-[2px]' : '';
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<span className={twMerge('text-content-secondary', 'inline-block', offsetCls, 'p-2')}>
|
|
42
|
+
{children}
|
|
43
|
+
</span>
|
|
44
|
+
<BlinkDot status={status} />
|
|
45
|
+
</>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 外部链接:使用 <a> 标签
|
|
50
|
+
if (external) {
|
|
51
|
+
return (
|
|
52
|
+
<a href={to} className={linkCls} target="_blank" rel="noreferrer" {...rest}>
|
|
53
|
+
{children}
|
|
54
|
+
</a>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 内部链接:使用 react-router-dom 的 Link
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<BaseLink to={to} className={linkCls} {...rest}>
|
|
62
|
+
{children}
|
|
63
|
+
<BlinkDot status={status} />
|
|
64
|
+
</BaseLink>
|
|
65
|
+
</>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
Link.displayName = 'Link';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { LinkVariants } from './Link.variants';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Link status for BlinkDot indicator
|
|
5
|
+
*/
|
|
6
|
+
export type LinkStatus = 'blink' | 'still' | 'hidden';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Link component Props
|
|
10
|
+
* 继承 react-router-dom 的 Link 属性
|
|
11
|
+
*/
|
|
12
|
+
export interface LinkProps
|
|
13
|
+
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>, LinkVariants {
|
|
14
|
+
/** 链接地址 */
|
|
15
|
+
to: string;
|
|
16
|
+
/** 自定义类名 */
|
|
17
|
+
className?: string;
|
|
18
|
+
/** 是否外部链接 */
|
|
19
|
+
external?: boolean;
|
|
20
|
+
/** 是否只读模式(不显示为链接) */
|
|
21
|
+
readonly?: boolean;
|
|
22
|
+
/** BlinkDot 状态 */
|
|
23
|
+
status?: LinkStatus;
|
|
24
|
+
/** 链接内容 */
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Link 变体定义
|
|
5
|
+
* 使用语义化颜色 token,支持 light/dark 主题切换
|
|
6
|
+
*/
|
|
7
|
+
export const linkVariants = cva(
|
|
8
|
+
[
|
|
9
|
+
'inline-block',
|
|
10
|
+
'rounded-sm',
|
|
11
|
+
'no-underline!',
|
|
12
|
+
'transition-all duration-200 ease-in-out',
|
|
13
|
+
'active:scale-[0.97]',
|
|
14
|
+
'p-2',
|
|
15
|
+
],
|
|
16
|
+
{
|
|
17
|
+
variants: {
|
|
18
|
+
variant: {
|
|
19
|
+
default: [
|
|
20
|
+
// 默认链接样式 - 使用 action 颜色(蓝色系)
|
|
21
|
+
'text-content-action',
|
|
22
|
+
'md:not-focus:hover:bg-secondary',
|
|
23
|
+
'md:not-focus:hover:text-content-action',
|
|
24
|
+
'focus:bg-secondary',
|
|
25
|
+
],
|
|
26
|
+
danger: [
|
|
27
|
+
// 危险/强调样式 - 使用 danger 颜色(粉色/红色系)
|
|
28
|
+
'text-content-danger',
|
|
29
|
+
'md:not-focus:hover:bg-accent/50',
|
|
30
|
+
'md:not-focus:hover:text-content-danger',
|
|
31
|
+
'focus:bg-accent/50',
|
|
32
|
+
],
|
|
33
|
+
special: [
|
|
34
|
+
// use special color
|
|
35
|
+
'text-content-special',
|
|
36
|
+
'md:not-focus:hover:bg-special',
|
|
37
|
+
'md:not-focus:hover:text-content-special',
|
|
38
|
+
'focus:bg-special',
|
|
39
|
+
],
|
|
40
|
+
muted: [
|
|
41
|
+
// 弱化样式 - 使用 secondary 颜色(灰色系)
|
|
42
|
+
'text-content-secondary',
|
|
43
|
+
'md:not-focus:hover:bg-muted',
|
|
44
|
+
'md:not-focus:hover:text-content-secondary',
|
|
45
|
+
'focus:bg-muted',
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
defaultVariants: {
|
|
50
|
+
variant: 'default',
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export type LinkVariants = VariantProps<typeof linkVariants>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Logo } from './Logo';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Logo> = {
|
|
5
|
+
title: 'Atoms/Logo',
|
|
6
|
+
component: Logo,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'centered',
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
args: {},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const Colored: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
className: 'text-content-primary',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Secondary: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
className: 'text-content-secondary',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Clickable: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
className: 'text-content-action cursor-pointer hover:opacity-80',
|
|
35
|
+
onClick: () => alert('Logo clicked!'),
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { LogoProps } from './Logo.types';
|
|
3
|
+
import { twMerge } from 'tailwind-merge';
|
|
4
|
+
|
|
5
|
+
export const Logo = React.memo(
|
|
6
|
+
(props: LogoProps) => (
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
width="48"
|
|
10
|
+
height="48"
|
|
11
|
+
viewBox="0 0 48 48"
|
|
12
|
+
fill="currentColor"
|
|
13
|
+
onClick={props.onClick}
|
|
14
|
+
className={twMerge(props.className, 'text-logo')}
|
|
15
|
+
>
|
|
16
|
+
<path
|
|
17
|
+
d="M29.1152 21.3106C32.0605 21.3106 34.4481 18.9101 34.4481 15.9489V24.6457C34.4481 25.7585 33.5508 26.6607 32.444 26.6607H15.1207C14.0138 26.6607 13.1166 25.7585 13.1166 24.6457V15.9489C13.1166 18.9101 15.5042 21.3106 18.4494 21.3106C21.3947 21.3106 23.7823 18.9101 23.7823 15.9489C23.7823 18.9101 26.17 21.3106 29.1152 21.3106Z"
|
|
18
|
+
className="fill-current"
|
|
19
|
+
/>
|
|
20
|
+
<path
|
|
21
|
+
d="M23.7823 15.9373L23.7823 15.9489C23.7823 15.9451 23.7823 15.9412 23.7823 15.9373Z"
|
|
22
|
+
className="fill-current"
|
|
23
|
+
/>
|
|
24
|
+
<path
|
|
25
|
+
d="M23.1143 28.004C23.1205 30.9598 25.5057 33.3541 28.4472 33.3541C31.3886 33.3541 33.7738 30.9598 33.7801 28.004H23.1143Z"
|
|
26
|
+
className="fill-current"
|
|
27
|
+
/>
|
|
28
|
+
<path
|
|
29
|
+
d="M13.7846 28.004C13.7846 28.0079 13.7846 28.0117 13.7846 28.0156C13.7908 30.9714 16.1761 33.3657 19.1175 33.3657C22.0589 33.3657 24.4442 30.9714 24.4504 28.0156H13.7846V28.004Z"
|
|
30
|
+
className="fill-current"
|
|
31
|
+
/>
|
|
32
|
+
<path
|
|
33
|
+
d="M14.4527 15.9373C14.4527 16.6792 13.8545 17.2806 13.1166 17.2806C12.3786 17.2806 11.7805 16.6792 11.7805 15.9373C11.7805 15.1954 12.3786 14.594 13.1166 14.594C13.8545 14.594 14.4527 15.1954 14.4527 15.9373Z"
|
|
34
|
+
className="fill-current"
|
|
35
|
+
/>
|
|
36
|
+
<path
|
|
37
|
+
d="M25.1184 15.2657C25.1184 16.0076 24.5202 16.609 23.7823 16.609C23.0444 16.609 22.4462 16.0076 22.4462 15.2657C22.4462 14.5238 23.0444 13.9224 23.7823 13.9224C24.5202 13.9224 25.1184 14.5238 25.1184 15.2657Z"
|
|
38
|
+
className="fill-current"
|
|
39
|
+
/>
|
|
40
|
+
<path
|
|
41
|
+
d="M35.7842 15.9373C35.7842 16.6792 35.186 17.2806 34.4481 17.2806C33.7102 17.2806 33.112 16.6792 33.112 15.9373C33.112 15.1954 33.7102 14.594 34.4481 14.594C35.186 14.594 35.7842 15.1954 35.7842 15.9373Z"
|
|
42
|
+
className="fill-current"
|
|
43
|
+
/>
|
|
44
|
+
</svg>
|
|
45
|
+
),
|
|
46
|
+
() => true
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
Logo.displayName = 'Logo';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Theme system
|
|
2
|
+
export { ThemeProvider, useTheme } from './theme';
|
|
3
|
+
export type { Theme, ThemeContextValue } from './theme';
|
|
4
|
+
|
|
5
|
+
// Atoms
|
|
6
|
+
export { Button, buttonVariants } from './atoms/button';
|
|
7
|
+
export type { ButtonProps } from './atoms/button';
|
|
8
|
+
export { BlinkDot } from './atoms/blink-dot';
|
|
9
|
+
export type { BlinkDotProps, BlinkDotStatus } from './atoms/blink-dot';
|
|
10
|
+
export { Link, linkVariants } from './atoms/link';
|
|
11
|
+
export type { LinkProps, LinkStatus } from './atoms/link';
|
|
12
|
+
export { Logo } from './atoms/logo';
|
|
13
|
+
export type { LogoProps } from './atoms/logo';
|
|
14
|
+
|
|
15
|
+
// Layout
|
|
16
|
+
export { Container } from './layout/container';
|
|
17
|
+
export type { ContainerProps } from './layout/container';
|
|
18
|
+
export { Grid } from './layout/grid';
|
|
19
|
+
export type { GridProps } from './layout/grid';
|
|
20
|
+
|
|
21
|
+
// Molecules
|
|
22
|
+
export { Breadcrumb } from './molecules/breadcrumb';
|
|
23
|
+
export type { BreadcrumbProps, PathObj } from './molecules/breadcrumb';
|
|
24
|
+
export { Panel } from './molecules/panel';
|
|
25
|
+
export type { PanelProps } from './molecules/panel';
|
|
26
|
+
export { Table } from './molecules/table';
|
|
27
|
+
export type { TableProps, TableHCellProps, TableCellProps } from './molecules/table';
|
|
28
|
+
export { List, TitledList, LinkList } from './molecules/list';
|
|
29
|
+
export type { ListProps, TitledListProps, LinkListProps } from './molecules/list';
|
|
30
|
+
export { Article } from './molecules/article';
|
|
31
|
+
export type { ArticleProps } from './molecules/article';
|
|
32
|
+
export { Nav } from './molecules/nav';
|
|
33
|
+
export type { NavProps } from './molecules/nav';
|
|
34
|
+
|
|
35
|
+
// Organisms
|
|
36
|
+
export { Page, ErrorBoundary, NotFound, Error } from './organisms/page';
|
|
37
|
+
export type {
|
|
38
|
+
PageProps,
|
|
39
|
+
ErrorBoundaryProps,
|
|
40
|
+
ErrorBoundaryState,
|
|
41
|
+
ErrorProps,
|
|
42
|
+
NotFoundProps,
|
|
43
|
+
} from './organisms/page';
|
|
44
|
+
export { DropZone } from './organisms/drop-zone';
|
|
45
|
+
export type { DropZoneProps } from './organisms/drop-zone';
|
|
46
|
+
export { DropImage, useDropImage } from './organisms/drop-image';
|
|
47
|
+
export type { DropImageProps, ImagePreviewerProps } from './organisms/drop-image';
|
|
48
|
+
export { Canvas, useRenderer } from './organisms/canvas';
|
|
49
|
+
export type { CanvasProps, AttributeProps, UniformProps } from './organisms/canvas';
|
|
50
|
+
export { LoadingSpiral } from './organisms/loading-spiral';
|
|
51
|
+
export type { LoadingSpiralProps } from './organisms/loading-spiral';
|
|
52
|
+
|
|
53
|
+
// Styles (side effect import)
|
|
54
|
+
import './styles.css';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Container } from './Container';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Container> = {
|
|
5
|
+
title: 'Layout/Container',
|
|
6
|
+
component: Container,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
argTypes: {
|
|
9
|
+
maxWidth: {
|
|
10
|
+
control: 'select',
|
|
11
|
+
options: ['sm', 'md', 'lg', 'xl', 'full'],
|
|
12
|
+
},
|
|
13
|
+
padding: {
|
|
14
|
+
control: 'select',
|
|
15
|
+
options: ['none', 'sm', 'md', 'lg'],
|
|
16
|
+
},
|
|
17
|
+
centered: {
|
|
18
|
+
control: 'boolean',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
type Story = StoryObj<typeof meta>;
|
|
25
|
+
|
|
26
|
+
export const Default: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
children: (
|
|
29
|
+
<div className="h-32 bg-[var(--semantic-color-muted)] rounded flex items-center justify-center">
|
|
30
|
+
Container Content
|
|
31
|
+
</div>
|
|
32
|
+
),
|
|
33
|
+
maxWidth: 'lg',
|
|
34
|
+
padding: 'md',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const MaxWidths: Story = {
|
|
39
|
+
render: () => (
|
|
40
|
+
<div className="space-y-4 w-full">
|
|
41
|
+
{(['sm', 'md', 'lg', 'xl'] as const).map(size => (
|
|
42
|
+
<Container
|
|
43
|
+
key={size}
|
|
44
|
+
maxWidth={size}
|
|
45
|
+
className="border border-dashed border-[var(--semantic-color-border)]"
|
|
46
|
+
>
|
|
47
|
+
<div className="h-16 bg-[var(--semantic-color-muted)] rounded flex items-center px-4 text-[var(--semantic-color-foreground)]">
|
|
48
|
+
maxWidth: {size}
|
|
49
|
+
</div>
|
|
50
|
+
</Container>
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const PaddingVariations: Story = {
|
|
57
|
+
render: () => (
|
|
58
|
+
<div className="space-y-4 w-full">
|
|
59
|
+
{(['none', 'sm', 'md', 'lg'] as const).map(pad => (
|
|
60
|
+
<Container
|
|
61
|
+
key={pad}
|
|
62
|
+
maxWidth="lg"
|
|
63
|
+
padding={pad}
|
|
64
|
+
className="border border-dashed border-[var(--semantic-color-border)]"
|
|
65
|
+
>
|
|
66
|
+
<div className="h-16 bg-[var(--semantic-color-muted)] rounded flex items-center px-4 text-[var(--semantic-color-foreground)]">
|
|
67
|
+
padding: {pad}
|
|
68
|
+
</div>
|
|
69
|
+
</Container>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
),
|
|
73
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
|
|
4
|
+
export interface ContainerProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
/** 最大宽度约束 */
|
|
8
|
+
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
9
|
+
/** 内边距 */
|
|
10
|
+
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
11
|
+
/** 居中对齐 */
|
|
12
|
+
centered?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const maxWidthMap = {
|
|
16
|
+
sm: 'max-w-[640px]',
|
|
17
|
+
md: 'max-w-[768px]',
|
|
18
|
+
lg: 'max-w-[1024px]',
|
|
19
|
+
xl: 'max-w-[1280px]',
|
|
20
|
+
full: 'max-w-none',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const paddingMap = {
|
|
24
|
+
none: 'px-0',
|
|
25
|
+
sm: 'px-4',
|
|
26
|
+
md: 'px-6',
|
|
27
|
+
lg: 'px-8',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Container 布局组件
|
|
32
|
+
*
|
|
33
|
+
* 提供响应式最大宽度和一致的内边距。
|
|
34
|
+
*/
|
|
35
|
+
export const Container: React.FC<ContainerProps> = ({
|
|
36
|
+
children,
|
|
37
|
+
className,
|
|
38
|
+
maxWidth = 'lg',
|
|
39
|
+
padding = 'md',
|
|
40
|
+
centered = true,
|
|
41
|
+
}) => {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className={twMerge(
|
|
45
|
+
'w-full',
|
|
46
|
+
maxWidthMap[maxWidth],
|
|
47
|
+
paddingMap[padding],
|
|
48
|
+
centered && 'mx-auto',
|
|
49
|
+
className
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
Container.displayName = 'Container';
|