Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
"@bfchain/util": "^5.0.0",
"@bfmeta/sign-util": "^1.3.10",
"@biochain/bio-sdk": "workspace:*",
"@biochain/key-ui": "workspace:*",
"@biochain/key-utils": "workspace:*",
"@biochain/plugin-navigation-sync": "workspace:*",
"@bnqkl/server-util": "^1.3.4",
"@bnqkl/wallet-sdk": "^0.23.8",
Expand Down
75 changes: 75 additions & 0 deletions packages/key-ui/src/alert/Alert.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { Alert } from './Alert'

const meta: Meta<typeof Alert> = {
title: 'Components/Alert',
component: Alert,
parameters: {
layout: 'padded',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['info', 'success', 'warning', 'error'],
},
},
}

export default meta
type Story = StoryObj<typeof meta>

const InfoIcon = () => (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="10" />
<path d="M12 16v-4M12 8h.01" />
</svg>
)

export const Info: Story = {
args: {
variant: 'info',
children: 'This is an informational message.',
icon: <InfoIcon />,
},
}

export const Success: Story = {
args: {
variant: 'success',
children: 'Your transaction was successful!',
icon: <InfoIcon />,
},
}

export const Warning: Story = {
args: {
variant: 'warning',
children: 'Please review before proceeding.',
icon: <InfoIcon />,
},
}

export const Error: Story = {
args: {
variant: 'error',
children: 'Something went wrong. Please try again.',
icon: <InfoIcon />,
},
}

export const WithTitle: Story = {
args: {
variant: 'warning',
title: 'Important Notice',
children: 'Your session will expire in 5 minutes.',
icon: <InfoIcon />,
},
}

export const NoIcon: Story = {
args: {
variant: 'info',
children: 'Alert without an icon.',
},
}
37 changes: 37 additions & 0 deletions packages/key-ui/src/alert/Alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client'
import * as React from 'react'
import { cn } from '@biochain/key-utils'

export type AlertVariant = 'info' | 'success' | 'warning' | 'error'

export interface AlertProps {
variant?: AlertVariant
title?: string
children: React.ReactNode
className?: string
icon?: React.ReactNode
}

const variantStyles: Record<AlertVariant, string> = {
info: 'bg-info/10 text-info',
success: 'bg-success/10 text-success',
warning: 'bg-warning/10 text-warning',
error: 'bg-destructive/10 text-destructive',
}

export function Alert({ variant = 'info', title, children, className, icon }: AlertProps) {
return (
<div role="alert" className={cn('flex gap-3 rounded-lg p-3 text-sm', variantStyles[variant], className)}>
{icon && <div className="mt-0.5 size-4 shrink-0">{icon}</div>}
<div className="flex-1">
{title && <p className="font-medium">{title}</p>}
<div className={title ? 'mt-1' : ''}>{children}</div>
</div>
</div>
)
}

export namespace Alert {
export type Props = AlertProps
export type Variant = AlertVariant
}
2 changes: 2 additions & 0 deletions packages/key-ui/src/alert/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Alert } from './Alert'
export type { AlertProps, AlertVariant } from './Alert'
54 changes: 54 additions & 0 deletions packages/key-ui/src/empty-state/EmptyState.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { EmptyState } from './EmptyState'

const meta: Meta<typeof EmptyState> = {
title: 'Components/EmptyState',
component: EmptyState,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
}

export default meta
type Story = StoryObj<typeof meta>

const PlaceholderIcon = () => (
<svg className="size-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
</svg>
)

export const Default: Story = {
args: {
title: 'No items found',
description: 'Get started by creating your first item.',
},
}

export const WithIcon: Story = {
args: {
icon: <PlaceholderIcon />,
title: 'No transactions',
description: 'Your transaction history will appear here.',
},
}

export const WithAction: Story = {
args: {
icon: <PlaceholderIcon />,
title: 'No wallet connected',
description: 'Connect your wallet to get started.',
action: (
<button className="bg-primary text-primary-foreground rounded-full px-6 py-2 font-medium">
Connect Wallet
</button>
),
},
}

export const Minimal: Story = {
args: {
title: 'Nothing here yet',
},
}
30 changes: 30 additions & 0 deletions packages/key-ui/src/empty-state/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'
import * as React from 'react'
import { cn } from '@biochain/key-utils'

export interface EmptyStateProps {
icon?: React.ReactNode
title: string
description?: string
action?: React.ReactNode
className?: string
testId?: string
}

export function EmptyState({ icon, title, description, action, className, testId }: EmptyStateProps) {
return (
<div
{...(testId && { 'data-testid': testId })}
className={cn('flex flex-col items-center justify-center px-4 py-12 text-center', className)}
>
{icon && <div className="text-muted-foreground mb-4">{icon}</div>}
<h3 className="text-foreground text-base font-medium @xs:text-lg">{title}</h3>
{description && <p className="text-muted-foreground mt-1 max-w-xs text-sm @xs:text-base">{description}</p>}
{action && <div className="mt-6">{action}</div>}
</div>
)
}

export namespace EmptyState {
export type Props = EmptyStateProps
}
2 changes: 2 additions & 0 deletions packages/key-ui/src/empty-state/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { EmptyState } from './EmptyState'
export type { EmptyStateProps } from './EmptyState'
104 changes: 104 additions & 0 deletions packages/key-ui/src/gradient-button/GradientButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { GradientButton } from './GradientButton'

const meta: Meta<typeof GradientButton> = {
title: 'Components/GradientButton',
component: GradientButton,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['blue', 'purple', 'red', 'mint'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
},
}

export default meta
type Story = StoryObj<typeof meta>

export const Purple: Story = {
args: {
variant: 'purple',
children: 'Continue',
},
}

export const Blue: Story = {
args: {
variant: 'blue',
children: 'Connect Wallet',
},
}

export const Red: Story = {
args: {
variant: 'red',
children: 'Disconnect',
},
}

export const Mint: Story = {
args: {
variant: 'mint',
children: 'Confirm',
},
}

export const Loading: Story = {
args: {
variant: 'purple',
children: 'Processing...',
loading: true,
},
}

export const Disabled: Story = {
args: {
variant: 'purple',
children: 'Disabled',
disabled: true,
},
}

export const FullWidth: Story = {
args: {
variant: 'purple',
children: 'Full Width Button',
fullWidth: true,
},
decorators: [
(Story) => (
<div className="w-80">
<Story />
</div>
),
],
}

export const AllSizes: Story = {
render: () => (
<div className="flex items-center gap-4">
<GradientButton size="sm">Small</GradientButton>
<GradientButton size="md">Medium</GradientButton>
<GradientButton size="lg">Large</GradientButton>
</div>
),
}

export const AllVariants: Story = {
render: () => (
<div className="flex flex-wrap gap-4">
<GradientButton variant="purple">Purple</GradientButton>
<GradientButton variant="blue">Blue</GradientButton>
<GradientButton variant="red">Red</GradientButton>
<GradientButton variant="mint">Mint</GradientButton>
</div>
),
}
Loading