@anker-in/headless-ui 0.0.2
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/dist/esm/components/badge.d.ts +9 -0
- package/dist/esm/components/button.d.ts +19 -0
- package/dist/esm/components/checkbox.d.ts +4 -0
- package/dist/esm/components/dialog.d.ts +19 -0
- package/dist/esm/components/heading.d.ts +7 -0
- package/dist/esm/components/index.d.ts +10 -0
- package/dist/esm/components/input.d.ts +21 -0
- package/dist/esm/components/popover.d.ts +6 -0
- package/dist/esm/components/radio.d.ts +5 -0
- package/dist/esm/components/skeleton.d.ts +6 -0
- package/dist/esm/components/text.d.ts +8 -0
- package/dist/esm/components/theme.d.ts +8 -0
- package/dist/esm/helpers/component-props.d.ts +7 -0
- package/dist/esm/helpers/constants.d.ts +2 -0
- package/dist/esm/helpers/index.d.ts +1 -0
- package/dist/esm/helpers/utils.d.ts +2 -0
- package/dist/esm/icons/spinner.d.ts +8 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +42 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/stories/badge.stories.d.ts +26 -0
- package/dist/esm/stories/button.stories.d.ts +39 -0
- package/dist/esm/stories/checkbox.stories.d.ts +6 -0
- package/dist/esm/stories/dialog.stories.d.ts +6 -0
- package/dist/esm/stories/input.stories.d.ts +30 -0
- package/dist/esm/stories/popover.stories.d.ts +6 -0
- package/dist/esm/stories/radio.stories.d.ts +6 -0
- package/dist/esm/stories/skeleton.stories.d.ts +14 -0
- package/dist/style.css +1352 -0
- package/package.json +64 -0
- package/src/components/__tests__/button.test.js +9 -0
- package/src/components/badge.tsx +29 -0
- package/src/components/button.tsx +92 -0
- package/src/components/checkbox.tsx +26 -0
- package/src/components/dialog.tsx +97 -0
- package/src/components/heading.tsx +23 -0
- package/src/components/index.ts +15 -0
- package/src/components/input.tsx +73 -0
- package/src/components/popover.tsx +29 -0
- package/src/components/radio.tsx +36 -0
- package/src/components/skeleton.tsx +10 -0
- package/src/components/text.tsx +22 -0
- package/src/components/theme.tsx +17 -0
- package/src/helpers/component-props.ts +17 -0
- package/src/helpers/constants.ts +3 -0
- package/src/helpers/index.ts +1 -0
- package/src/helpers/utils.ts +6 -0
- package/src/icons/spinner.tsx +30 -0
- package/src/index.ts +1 -0
- package/src/stories/badge.stories.tsx +44 -0
- package/src/stories/button.stories.tsx +90 -0
- package/src/stories/checkbox.stories.tsx +16 -0
- package/src/stories/dialog.stories.tsx +45 -0
- package/src/stories/input.stories.tsx +59 -0
- package/src/stories/popover.stories.tsx +24 -0
- package/src/stories/radio.stories.tsx +19 -0
- package/src/stories/skeleton.stories.tsx +33 -0
- package/src/styles/global.css +196 -0
- package/src/styles/index.css +0 -0
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@anker-in/headless-ui",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"main": "./dist/esm/index.js",
|
|
5
|
+
"types": "./dist/esm/index.d.ts",
|
|
6
|
+
"module": "./dist/esm/index.js",
|
|
7
|
+
"style": "./dist/style.css",
|
|
8
|
+
"description": "",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"files": [
|
|
11
|
+
"src/**",
|
|
12
|
+
"dist/**"
|
|
13
|
+
],
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"keywords": [],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@babel/preset-env": "^7.24.3",
|
|
20
|
+
"@babel/preset-react": "^7.24.1",
|
|
21
|
+
"@babel/preset-typescript": "^7.24.1",
|
|
22
|
+
"@testing-library/react": "^14.2.2",
|
|
23
|
+
"babel-jest": "^29.7.0",
|
|
24
|
+
"esbuild": "^0.20.2",
|
|
25
|
+
"esbuild-style-plugin": "^1.6.3",
|
|
26
|
+
"eslint-plugin-require-extensions": "^0.1.3",
|
|
27
|
+
"jest": "^29.7.0",
|
|
28
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
29
|
+
"postcss-cli": "^11.0.0",
|
|
30
|
+
"react-test-renderer": "^18.2.0",
|
|
31
|
+
"stylelint": "^16.2.1"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@radix-ui/react-checkbox": "^1.0.4",
|
|
35
|
+
"@radix-ui/react-dialog": "^1.0.5",
|
|
36
|
+
"@radix-ui/react-icons": "^1.3.0",
|
|
37
|
+
"@radix-ui/react-popover": "^1.0.7",
|
|
38
|
+
"@radix-ui/react-radio-group": "^1.1.3",
|
|
39
|
+
"@radix-ui/react-slot": "^1.0.2",
|
|
40
|
+
"@radix-ui/react-visually-hidden": "^1.0.3",
|
|
41
|
+
"autoprefixer": "^10.4.18",
|
|
42
|
+
"class-variance-authority": "^0.7.0",
|
|
43
|
+
"clsx": "^2.1.0",
|
|
44
|
+
"postcss": "^8.4.37",
|
|
45
|
+
"tailwind-merge": "^2.2.2",
|
|
46
|
+
"tailwindcss": "^3.4.1"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public",
|
|
50
|
+
"registry": "https://registry.npmjs.org/"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"dev": "pnpm run dev:js:esm & pnpm run dev:js:esm:types & pnpm run dev:css",
|
|
54
|
+
"dev:js:esm": "node build.esm.js watch=true",
|
|
55
|
+
"dev:js:esm:types": "tsc --watch --incremental --outdir dist/esm >/dev/null",
|
|
56
|
+
"dev:css": "postcss src/styles/global.css -o dist/style.css --watch",
|
|
57
|
+
"build": "pnpm run build:css & pnpm run build:js:esm & pnpm run build:js:esm:types ",
|
|
58
|
+
"build:js:esm": "node build.esm.js",
|
|
59
|
+
"build:js:esm:types": "tsc --outdir dist/esm",
|
|
60
|
+
"build:css": "postcss src/styles/global.css -o dist/style.css",
|
|
61
|
+
"lint": "eslint \"src/**/*.ts*\"",
|
|
62
|
+
"test": "jest"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { cleanup, fireEvent, render } from '@testing-library/react'
|
|
2
|
+
import Button from '../button'
|
|
3
|
+
|
|
4
|
+
test('button click should call the onClick handler', () => {
|
|
5
|
+
const onClick = jest.fn()
|
|
6
|
+
const { getByText } = render(<Button onClick={onClick}>Click me</Button>)
|
|
7
|
+
fireEvent.click(getByText('Click me'))
|
|
8
|
+
expect(onClick).toHaveBeenCalled()
|
|
9
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
3
|
+
|
|
4
|
+
import { cn } from '../helpers/index'
|
|
5
|
+
|
|
6
|
+
const badgeVariants = cva(
|
|
7
|
+
'inline-flex items-center rounded-[20px] border px-[10px] py-1 text-xs font-medium transition-colors focus:outline-none',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-badge text-badge-foreground border-transparent',
|
|
12
|
+
secondary: 'bg-secondary text-secondary-foreground border-transparent',
|
|
13
|
+
destructive: 'bg-destructive text-destructive-foreground border-transparent',
|
|
14
|
+
outline: 'text-foreground',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
variant: 'default',
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
|
|
24
|
+
|
|
25
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
26
|
+
return <div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default Badge
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot'
|
|
3
|
+
import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
|
|
4
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
|
+
import { cn } from '../helpers/index'
|
|
6
|
+
import Spinner from '../icons/spinner'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Button Variants
|
|
10
|
+
*/
|
|
11
|
+
const buttonVariants = cva(
|
|
12
|
+
'ring-offset-background focus-visible:ring-ring rounded-btn inline-flex items-center justify-center whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
primary:
|
|
17
|
+
'bg-btn-primary text-btn-primary-foreground hover:bg-btn-primary-active disabled:bg-btn-primary-disabled disabled:text-btn-primary-disabled-foreground',
|
|
18
|
+
secondary:
|
|
19
|
+
'bg-btn-secondary border-primary text-btn-secondary-foreground hover:bg-btn-secondary-active hover:text-btn-secondary disabled:bg-btn-secondary-disabled disabled:text-btn-secondary-disabled-foreground border border-solid',
|
|
20
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
21
|
+
ghost: 'hover:bg-btn-primary hover:text-btn-primary-foreground',
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
sm: 'h-8 px-3 py-1',
|
|
25
|
+
base: 'h-10 px-4 py-2',
|
|
26
|
+
lg: 'h-11 px-8',
|
|
27
|
+
icon: 'size-10 rounded-full',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: 'primary',
|
|
32
|
+
size: 'base',
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
export interface ButtonProps
|
|
38
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
39
|
+
VariantProps<typeof buttonVariants> {
|
|
40
|
+
/** 设置按钮载入状态 */
|
|
41
|
+
loading?: boolean
|
|
42
|
+
/** 设置按钮失效状态 */
|
|
43
|
+
disabled?: boolean
|
|
44
|
+
/** 定义自组件作为父组件的类型, 详细使用方式请参考radix-ui */
|
|
45
|
+
asChild?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
49
|
+
(
|
|
50
|
+
{
|
|
51
|
+
asChild = false,
|
|
52
|
+
size = 'base',
|
|
53
|
+
children,
|
|
54
|
+
variant,
|
|
55
|
+
className,
|
|
56
|
+
disabled = false,
|
|
57
|
+
loading = false,
|
|
58
|
+
...props
|
|
59
|
+
}: ButtonProps,
|
|
60
|
+
ref
|
|
61
|
+
) => {
|
|
62
|
+
const Comp = asChild ? Slot : 'button'
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Comp
|
|
66
|
+
disabled={disabled}
|
|
67
|
+
className={cn(buttonVariants({ variant, size, className }), {
|
|
68
|
+
'cursor-not-allowed': disabled,
|
|
69
|
+
})}
|
|
70
|
+
ref={ref}
|
|
71
|
+
{...props}
|
|
72
|
+
>
|
|
73
|
+
{loading ? (
|
|
74
|
+
<>
|
|
75
|
+
<span style={{ display: 'contents', visibility: 'hidden' }} aria-hidden>
|
|
76
|
+
{children}
|
|
77
|
+
</span>
|
|
78
|
+
<span className="absolute">
|
|
79
|
+
<Spinner />
|
|
80
|
+
</span>
|
|
81
|
+
<VisuallyHidden>{children}</VisuallyHidden>
|
|
82
|
+
</>
|
|
83
|
+
) : (
|
|
84
|
+
children
|
|
85
|
+
)}
|
|
86
|
+
</Comp>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
Button.displayName = 'Button'
|
|
92
|
+
export default Button
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
|
|
3
|
+
import { CheckIcon } from '@radix-ui/react-icons'
|
|
4
|
+
|
|
5
|
+
import { cn } from '../helpers/index'
|
|
6
|
+
|
|
7
|
+
const Checkbox = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
|
10
|
+
>(({ className, ...props }, ref) => (
|
|
11
|
+
<CheckboxPrimitive.Root
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn(
|
|
14
|
+
'border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer size-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
>
|
|
19
|
+
<CheckboxPrimitive.Indicator className={cn('flex items-center justify-center text-current')}>
|
|
20
|
+
<CheckIcon className="size-4" />
|
|
21
|
+
</CheckboxPrimitive.Indicator>
|
|
22
|
+
</CheckboxPrimitive.Root>
|
|
23
|
+
))
|
|
24
|
+
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
|
25
|
+
|
|
26
|
+
export default Checkbox
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
|
5
|
+
import { Cross2Icon } from '@radix-ui/react-icons'
|
|
6
|
+
|
|
7
|
+
import { cn } from '../helpers/index'
|
|
8
|
+
|
|
9
|
+
const Dialog = DialogPrimitive.Root
|
|
10
|
+
|
|
11
|
+
const DialogTrigger = DialogPrimitive.Trigger
|
|
12
|
+
|
|
13
|
+
const DialogPortal = DialogPrimitive.Portal
|
|
14
|
+
|
|
15
|
+
const DialogClose = DialogPrimitive.Close
|
|
16
|
+
|
|
17
|
+
const DialogOverlay = React.forwardRef<
|
|
18
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
19
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
20
|
+
>(({ className, ...props }, ref) => (
|
|
21
|
+
<DialogPrimitive.Overlay
|
|
22
|
+
ref={ref}
|
|
23
|
+
className={cn(
|
|
24
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
))
|
|
30
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
31
|
+
|
|
32
|
+
const DialogContent = React.forwardRef<
|
|
33
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
34
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
35
|
+
>(({ className, children, ...props }, ref) => (
|
|
36
|
+
<DialogPortal>
|
|
37
|
+
<DialogOverlay />
|
|
38
|
+
<DialogPrimitive.Content
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn(
|
|
41
|
+
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
|
|
48
|
+
<Cross2Icon className="size-4" />
|
|
49
|
+
<span className="sr-only">Close</span>
|
|
50
|
+
</DialogPrimitive.Close>
|
|
51
|
+
</DialogPrimitive.Content>
|
|
52
|
+
</DialogPortal>
|
|
53
|
+
))
|
|
54
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName
|
|
55
|
+
|
|
56
|
+
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
57
|
+
<div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
|
|
58
|
+
)
|
|
59
|
+
DialogHeader.displayName = 'DialogHeader'
|
|
60
|
+
|
|
61
|
+
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
62
|
+
<div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />
|
|
63
|
+
)
|
|
64
|
+
DialogFooter.displayName = 'DialogFooter'
|
|
65
|
+
|
|
66
|
+
const DialogTitle = React.forwardRef<
|
|
67
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
68
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
69
|
+
>(({ className, ...props }, ref) => (
|
|
70
|
+
<DialogPrimitive.Title
|
|
71
|
+
ref={ref}
|
|
72
|
+
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
))
|
|
76
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
|
77
|
+
|
|
78
|
+
const DialogDescription = React.forwardRef<
|
|
79
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
80
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
81
|
+
>(({ className, ...props }, ref) => (
|
|
82
|
+
<DialogPrimitive.Description ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
|
|
83
|
+
))
|
|
84
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
Dialog,
|
|
88
|
+
DialogPortal,
|
|
89
|
+
DialogOverlay,
|
|
90
|
+
DialogClose,
|
|
91
|
+
DialogTrigger,
|
|
92
|
+
DialogContent,
|
|
93
|
+
DialogHeader,
|
|
94
|
+
DialogFooter,
|
|
95
|
+
DialogTitle,
|
|
96
|
+
DialogDescription,
|
|
97
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cn } from '@/helpers'
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
type HeadingElement = React.ElementRef<'h1'>
|
|
6
|
+
|
|
7
|
+
interface HeadingProps extends React.ComponentPropsWithoutRef<'h1'> {
|
|
8
|
+
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | undefined
|
|
9
|
+
asChild?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Heading = React.forwardRef<HeadingElement, HeadingProps>((props, forwardedRef) => {
|
|
13
|
+
const { children, className, asChild, as: Tag = 'h1', color, ...headingProps } = props
|
|
14
|
+
return (
|
|
15
|
+
<Slot data-accent-color={color || undefined} {...headingProps} ref={forwardedRef} className={cn('', className)}>
|
|
16
|
+
{asChild ? children : <Tag>{children}</Tag>}
|
|
17
|
+
</Slot>
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
Heading.displayName = 'Heading'
|
|
22
|
+
|
|
23
|
+
export { Heading }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// theme
|
|
2
|
+
export { default as Theme } from './theme'
|
|
3
|
+
|
|
4
|
+
// components
|
|
5
|
+
export { default as Button } from './button'
|
|
6
|
+
export { default as Badge } from './badge'
|
|
7
|
+
export * from './input'
|
|
8
|
+
|
|
9
|
+
export { default as Checkbox } from './checkbox'
|
|
10
|
+
|
|
11
|
+
export { default as Skeleton } from './skeleton'
|
|
12
|
+
export * from './dialog'
|
|
13
|
+
export * from './popover'
|
|
14
|
+
export * from './radio'
|
|
15
|
+
export * from './dialog'
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { cn } from '../helpers/index'
|
|
3
|
+
import { ComponentPropsWithout, RemovedProps } from '@/helpers/component-props'
|
|
4
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
5
|
+
|
|
6
|
+
const inputVariants = cva(
|
|
7
|
+
'border-input bg-background focus-visible:ring-ring ring-offset-background flex h-2 flex-row items-center overflow-hidden rounded-md border px-3 focus-visible:ring-2 focus-visible:ring-offset-2',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: 'h-8',
|
|
12
|
+
base: 'h-10',
|
|
13
|
+
lg: 'h-12',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
size: 'base',
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
export interface InputProps extends ComponentPropsWithout<'input', 'size'>, VariantProps<typeof inputVariants> {}
|
|
23
|
+
|
|
24
|
+
type InputSlotElement = React.ElementRef<'div'>
|
|
25
|
+
interface InputSlotProps extends ComponentPropsWithout<'div', RemovedProps> {
|
|
26
|
+
/**
|
|
27
|
+
* 插槽的位置
|
|
28
|
+
*/
|
|
29
|
+
side: 'left' | 'right'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* InputSlot
|
|
34
|
+
* 用来在 Input 组件中插入额外的内容,比如icon ,按钮
|
|
35
|
+
*/
|
|
36
|
+
const InputSlot = React.forwardRef<InputSlotElement, InputSlotProps>((props, forwardedRef) => {
|
|
37
|
+
const { className, side, children, ...slotProps } = props
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
ref={forwardedRef}
|
|
42
|
+
{...slotProps}
|
|
43
|
+
className={cn('flex h-full items-center px-2', className, {
|
|
44
|
+
'-order-1 pl-0': side === 'left',
|
|
45
|
+
'order-2 pr-0': side === 'right',
|
|
46
|
+
})}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</div>
|
|
50
|
+
)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const Input = React.forwardRef<React.ElementRef<'input'>, InputProps>(
|
|
54
|
+
({ className, type, size, children, ...props }, ref) => {
|
|
55
|
+
return (
|
|
56
|
+
<div className={cn(inputVariants({ size }), className)}>
|
|
57
|
+
<input
|
|
58
|
+
type={type}
|
|
59
|
+
className={cn(
|
|
60
|
+
'placeholder:text-muted-foreground flex size-full text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50'
|
|
61
|
+
)}
|
|
62
|
+
ref={ref}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
{children}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
Input.displayName = 'Input'
|
|
71
|
+
InputSlot.displayName = 'InputSlot'
|
|
72
|
+
|
|
73
|
+
export { Input, InputSlot }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
|
3
|
+
|
|
4
|
+
import { cn } from '../helpers/utils'
|
|
5
|
+
|
|
6
|
+
const Popover = PopoverPrimitive.Root
|
|
7
|
+
|
|
8
|
+
const PopoverTrigger = PopoverPrimitive.Trigger
|
|
9
|
+
|
|
10
|
+
const PopoverContent = React.forwardRef<
|
|
11
|
+
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
12
|
+
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
|
13
|
+
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
|
|
14
|
+
<PopoverPrimitive.Portal>
|
|
15
|
+
<PopoverPrimitive.Content
|
|
16
|
+
ref={ref}
|
|
17
|
+
align={align}
|
|
18
|
+
sideOffset={sideOffset}
|
|
19
|
+
className={cn(
|
|
20
|
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none',
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
</PopoverPrimitive.Portal>
|
|
26
|
+
))
|
|
27
|
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
|
28
|
+
|
|
29
|
+
export { Popover, PopoverTrigger, PopoverContent }
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
|
|
3
|
+
import { CheckIcon } from '@radix-ui/react-icons'
|
|
4
|
+
|
|
5
|
+
import { cn } from '../helpers/index'
|
|
6
|
+
|
|
7
|
+
const RadioGroup = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
|
|
10
|
+
>(({ className, ...props }, ref) => {
|
|
11
|
+
return <RadioGroupPrimitive.Root className={cn('grid gap-2', className)} {...props} ref={ref} />
|
|
12
|
+
})
|
|
13
|
+
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
|
|
14
|
+
|
|
15
|
+
const RadioGroupItem = React.forwardRef<
|
|
16
|
+
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
|
17
|
+
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
|
|
18
|
+
>(({ className, ...props }, ref) => {
|
|
19
|
+
return (
|
|
20
|
+
<RadioGroupPrimitive.Item
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={cn(
|
|
23
|
+
'border-primary text-primary ring-offset-background focus-visible:ring-ring aspect-square size-4 rounded-full border focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
|
29
|
+
<CheckIcon className="text-primary size-2.5 fill-current" />
|
|
30
|
+
</RadioGroupPrimitive.Indicator>
|
|
31
|
+
</RadioGroupPrimitive.Item>
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
|
|
35
|
+
|
|
36
|
+
export { RadioGroup, RadioGroupItem }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { cn } from '../helpers/index'
|
|
3
|
+
|
|
4
|
+
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
5
|
+
return <div className={cn('bg-skeleton size-12 animate-pulse', className)} {...props} />
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
Skeleton.displayName = 'Skeleton'
|
|
9
|
+
|
|
10
|
+
export default Skeleton
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cn } from '@/helpers'
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
type TextElement = React.ElementRef<'span'>
|
|
6
|
+
interface TextProps extends React.ComponentPropsWithoutRef<'span'> {
|
|
7
|
+
as?: 'div' | 'label' | 'p' | 'span' | undefined
|
|
8
|
+
asChild?: boolean
|
|
9
|
+
color?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Text = React.forwardRef<TextElement, TextProps>((props, forwardedRef) => {
|
|
13
|
+
const { children, className, asChild, as: Tag = 'span', color, ...textProps } = props
|
|
14
|
+
return (
|
|
15
|
+
<Slot {...textProps} ref={forwardedRef} className={cn(className, color)}>
|
|
16
|
+
{asChild ? children : <Tag>{children}</Tag>}
|
|
17
|
+
</Slot>
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
Text.displayName = 'Text'
|
|
21
|
+
|
|
22
|
+
export { Text }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Brand } from '@/helpers/constants'
|
|
2
|
+
import React, { useEffect } from 'react'
|
|
3
|
+
|
|
4
|
+
interface ThemeProps {
|
|
5
|
+
brand: Brand
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
}
|
|
8
|
+
const Theme = ({ brand, children }: ThemeProps) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
// TODO: 在ssr 中不生效, 暂时手动在外层设置
|
|
11
|
+
document.getElementsByTagName('html')?.[0]?.setAttribute('data-brand-theme', brand)
|
|
12
|
+
}, [brand])
|
|
13
|
+
|
|
14
|
+
return <>{children}</>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default Theme
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
|
|
3
|
+
type ComponentPropsAs<C extends React.ElementType<any>, T extends React.ComponentPropsWithoutRef<C>['as']> = Omit<
|
|
4
|
+
Extract<React.ComponentPropsWithoutRef<C>, { as: T }>,
|
|
5
|
+
'as' | 'asChild'
|
|
6
|
+
>
|
|
7
|
+
|
|
8
|
+
// Omits the specified props from the component props. Autocomplete will suggest props
|
|
9
|
+
// of the component, but won't restrict the omittable props to those that actually exist.
|
|
10
|
+
type ComponentPropsWithout<
|
|
11
|
+
T extends React.ElementType,
|
|
12
|
+
O extends Omit<string, keyof React.ComponentPropsWithoutRef<T>> | keyof React.ComponentPropsWithoutRef<T>,
|
|
13
|
+
> = Omit<React.ComponentPropsWithoutRef<T>, O & string>
|
|
14
|
+
|
|
15
|
+
type RemovedProps = 'asChild' | 'defaultChecked' | 'defaultValue' | 'color'
|
|
16
|
+
|
|
17
|
+
export type { ComponentPropsAs, ComponentPropsWithout, RemovedProps }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { cn } from './utils'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
export interface SpinnerProps {
|
|
4
|
+
/** loading 图标大小 */
|
|
5
|
+
size?: 'sm' | 'md' | 'lg'
|
|
6
|
+
color?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line no-unused-vars
|
|
10
|
+
const Spinner = ({ size = 'sm', color = '#ffffff' }: SpinnerProps) => {
|
|
11
|
+
return (
|
|
12
|
+
<svg
|
|
13
|
+
className="animate-spin"
|
|
14
|
+
width="20"
|
|
15
|
+
height="20"
|
|
16
|
+
viewBox="0 0 20 20"
|
|
17
|
+
fill="none"
|
|
18
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
19
|
+
>
|
|
20
|
+
<path
|
|
21
|
+
d="M3 10C3 13.866 6.13401 17 10 17C13.866 17 17 13.866 17 10C17 6.13401 13.866 3 10 3"
|
|
22
|
+
stroke={color}
|
|
23
|
+
strokeWidth="1.6"
|
|
24
|
+
strokeLinecap="round"
|
|
25
|
+
/>
|
|
26
|
+
</svg>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default Spinner
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/index'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
|
|
3
|
+
import { Badge } from '../components/index'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Components/Badge',
|
|
8
|
+
component: Badge,
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: 'centered',
|
|
11
|
+
},
|
|
12
|
+
tags: ['autodocs'],
|
|
13
|
+
argTypes: {
|
|
14
|
+
variant: {
|
|
15
|
+
control: {
|
|
16
|
+
type: 'select',
|
|
17
|
+
},
|
|
18
|
+
options: ['default', 'destructive', 'outline', 'secondary'],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
variant: 'default',
|
|
23
|
+
children: 'New',
|
|
24
|
+
},
|
|
25
|
+
} satisfies Meta<typeof Badge>
|
|
26
|
+
|
|
27
|
+
export default meta
|
|
28
|
+
|
|
29
|
+
type Story = StoryObj<typeof meta>
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {}
|
|
32
|
+
|
|
33
|
+
export const Examples: Story = {
|
|
34
|
+
render() {
|
|
35
|
+
return (
|
|
36
|
+
<div className="flex flex-col space-y-4">
|
|
37
|
+
<Badge variant="default">Default</Badge>
|
|
38
|
+
<Badge variant="secondary">Secondary</Badge>
|
|
39
|
+
<Badge variant="destructive">Destructive</Badge>
|
|
40
|
+
<Badge variant="outline">Outline</Badge>
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
},
|
|
44
|
+
}
|