[WEB-4733] dev: propel toolbar component (#7742)
* dev: toolbar component added to propel * dev: toolbar story added * chore: propel config updated * chore: code refactor --------- Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
parent
5a63e6dad2
commit
3b8bb1effc
5 changed files with 292 additions and 0 deletions
|
|
@ -38,6 +38,7 @@
|
|||
"./table": "./dist/table/index.js",
|
||||
"./tabs": "./dist/tabs/index.js",
|
||||
"./toast": "./dist/toast/index.js",
|
||||
"./toolbar": "./dist/toolbar/index.js",
|
||||
"./tooltip": "./dist/tooltip/index.js",
|
||||
"./utils": "./dist/utils/index.js"
|
||||
},
|
||||
|
|
|
|||
8
packages/propel/src/toolbar/index.ts
Normal file
8
packages/propel/src/toolbar/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export { Toolbar } from "./toolbar";
|
||||
export type {
|
||||
ToolbarProps,
|
||||
ToolbarGroupProps,
|
||||
ToolbarItemProps,
|
||||
ToolbarSeparatorProps,
|
||||
ToolbarSubmitButtonProps,
|
||||
} from "./toolbar";
|
||||
123
packages/propel/src/toolbar/toolbar.stories.tsx
Normal file
123
packages/propel/src/toolbar/toolbar.stories.tsx
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import {
|
||||
Bold,
|
||||
Italic,
|
||||
Underline,
|
||||
Strikethrough,
|
||||
Code,
|
||||
Link,
|
||||
List,
|
||||
ListOrdered,
|
||||
Quote,
|
||||
AlignLeft,
|
||||
AlignCenter,
|
||||
AlignRight,
|
||||
Undo,
|
||||
Redo,
|
||||
Globe2,
|
||||
Lock,
|
||||
} from "lucide-react";
|
||||
import { Toolbar } from "./toolbar";
|
||||
|
||||
const meta: Meta<typeof Toolbar> = {
|
||||
title: "Components/Toolbar",
|
||||
component: Toolbar,
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Toolbar>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="w-96 border rounded">
|
||||
<Toolbar>
|
||||
<Toolbar.Group isFirst>
|
||||
<Toolbar.Item icon={Undo} tooltip="Undo" />
|
||||
<Toolbar.Item icon={Redo} tooltip="Redo" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={Bold} tooltip="Bold" />
|
||||
<Toolbar.Item icon={Italic} tooltip="Italic" />
|
||||
<Toolbar.Item icon={Underline} tooltip="Underline" />
|
||||
<Toolbar.Item icon={Strikethrough} tooltip="Strikethrough" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={List} tooltip="Bullet List" />
|
||||
<Toolbar.Item icon={ListOrdered} tooltip="Numbered List" />
|
||||
<Toolbar.Item icon={Quote} tooltip="Quote" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={AlignLeft} tooltip="Align Left" />
|
||||
<Toolbar.Item icon={AlignCenter} tooltip="Align Center" />
|
||||
<Toolbar.Item icon={AlignRight} tooltip="Align Right" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={Link} tooltip="Link" />
|
||||
<Toolbar.Item icon={Code} tooltip="Code" />
|
||||
</Toolbar.Group>
|
||||
</Toolbar>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithActiveStates: Story = {
|
||||
render: () => (
|
||||
<div className="p-4">
|
||||
<Toolbar>
|
||||
<Toolbar.Group isFirst>
|
||||
<Toolbar.Item icon={Bold} tooltip="Bold" shortcut={["Cmd", "B"]} isActive />
|
||||
<Toolbar.Item icon={Italic} tooltip="Italic" shortcut={["Cmd", "I"]} />
|
||||
<Toolbar.Item icon={Underline} tooltip="Underline" shortcut={["Cmd", "U"]} isActive />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={List} tooltip="Bullet List" />
|
||||
<Toolbar.Item icon={ListOrdered} tooltip="Numbered List" isActive />
|
||||
<Toolbar.Item icon={Quote} tooltip="Quote" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={AlignLeft} tooltip="Align Left" />
|
||||
<Toolbar.Item icon={AlignCenter} tooltip="Align Center" isActive />
|
||||
<Toolbar.Item icon={AlignRight} tooltip="Align Right" />
|
||||
</Toolbar.Group>
|
||||
</Toolbar>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const CommentToolbar: Story = {
|
||||
render: () => (
|
||||
<div className="p-4 space-y-4">
|
||||
<h3 className="text-sm font-medium">Comment Toolbar with Access Control</h3>
|
||||
<div className="rounded border-[0.5px] border-custom-border-200 p-1">
|
||||
<Toolbar>
|
||||
{/* Access Specifier */}
|
||||
<div className="flex flex-shrink-0 items-stretch gap-0.5 rounded border-[0.5px] border-custom-border-200 p-1">
|
||||
<Toolbar.Item icon={Lock} tooltip="Private" isActive />
|
||||
<Toolbar.Item icon={Globe2} tooltip="Public" />
|
||||
</div>
|
||||
|
||||
<div className="flex w-full items-stretch justify-between gap-2 rounded border-[0.5px] border-custom-border-200 p-1">
|
||||
<div className="flex items-stretch">
|
||||
<Toolbar.Group isFirst>
|
||||
<Toolbar.Item icon={Bold} tooltip="Bold" shortcut={["Cmd", "B"]} />
|
||||
<Toolbar.Item icon={Italic} tooltip="Italic" shortcut={["Cmd", "I"]} />
|
||||
<Toolbar.Item icon={Code} tooltip="Code" shortcut={["Cmd", "`"]} />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={List} tooltip="Bullet List" />
|
||||
<Toolbar.Item icon={ListOrdered} tooltip="Numbered List" />
|
||||
</Toolbar.Group>
|
||||
</div>
|
||||
<Toolbar.SubmitButton>Comment</Toolbar.SubmitButton>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
159
packages/propel/src/toolbar/toolbar.tsx
Normal file
159
packages/propel/src/toolbar/toolbar.tsx
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import * as React from "react";
|
||||
import { LucideIcon } from "lucide-react";
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { cn } from "../utils";
|
||||
|
||||
export interface ToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface ToolbarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
isFirst?: boolean;
|
||||
}
|
||||
|
||||
export interface ToolbarItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
icon: LucideIcon;
|
||||
isActive?: boolean;
|
||||
tooltip?: string;
|
||||
shortcut?: string[];
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface ToolbarSeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface ToolbarSubmitButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
loading?: boolean;
|
||||
variant?: "primary" | "secondary" | "outline" | "ghost" | "destructive";
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const ToolbarRoot = React.forwardRef<HTMLDivElement, ToolbarProps>(({ className, children, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex h-9 w-full items-stretch gap-1.5 bg-custom-background-90 overflow-x-scroll", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
));
|
||||
|
||||
const ToolbarGroup = React.forwardRef<HTMLDivElement, ToolbarGroupProps>(
|
||||
({ className, children, isFirst = false, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex items-stretch gap-0.5 border-r border-custom-border-200 px-2.5",
|
||||
{
|
||||
"pl-0": isFirst,
|
||||
},
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const ToolbarItem = React.forwardRef<HTMLButtonElement, ToolbarItemProps>(
|
||||
({ icon: Icon, isActive = false, tooltip, shortcut, className, children, ...props }, ref) => {
|
||||
const button = (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className={cn(
|
||||
"grid place-items-center aspect-square rounded-sm p-0.5 text-custom-text-400 hover:bg-custom-background-80 transition-colors",
|
||||
{
|
||||
"bg-custom-background-80 text-custom-text-100": isActive,
|
||||
},
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Icon
|
||||
className={cn("h-3.5 w-3.5", {
|
||||
"text-custom-text-100": isActive,
|
||||
})}
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipContent={
|
||||
<div className="flex flex-col gap-1 text-center text-xs">
|
||||
<span className="font-medium">{tooltip}</span>
|
||||
{shortcut && <kbd className="text-custom-text-400">{shortcut.join(" + ")}</kbd>}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{button}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
);
|
||||
|
||||
const ToolbarSeparator = React.forwardRef<HTMLDivElement, ToolbarSeparatorProps>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("h-full w-px bg-custom-border-200 mx-1", className)} {...props} />
|
||||
));
|
||||
|
||||
const buttonVariants = {
|
||||
primary: "bg-custom-primary-100 text-white hover:bg-custom-primary-200 focus:bg-custom-primary-200",
|
||||
secondary:
|
||||
"bg-custom-background-100 text-custom-text-200 border border-custom-border-200 hover:bg-custom-background-90 focus:bg-custom-background-90",
|
||||
outline:
|
||||
"border border-custom-primary-100 text-custom-primary-100 bg-transparent hover:bg-custom-primary-100/10 focus:bg-custom-primary-100/20",
|
||||
ghost: "text-custom-text-200 hover:bg-custom-background-90 focus:bg-custom-background-90",
|
||||
destructive: "bg-red-500 text-white hover:bg-red-600 focus:bg-red-600",
|
||||
};
|
||||
|
||||
const ToolbarSubmitButton = React.forwardRef<HTMLButtonElement, ToolbarSubmitButtonProps>(
|
||||
({ loading = false, variant = "primary", className, children, disabled, ...props }, ref) => (
|
||||
<div className="sticky right-1">
|
||||
<button
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center gap-2 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors duration-200",
|
||||
"focus:outline-none focus:ring-2 focus:ring-custom-primary-100/20 focus:ring-offset-2",
|
||||
"disabled:opacity-50 disabled:pointer-events-none",
|
||||
buttonVariants[variant],
|
||||
className
|
||||
)}
|
||||
disabled={disabled || loading}
|
||||
{...props}
|
||||
>
|
||||
{loading && <div className="h-3 w-3 animate-spin rounded-full border border-current border-t-transparent" />}
|
||||
{children}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
ToolbarRoot.displayName = "ToolbarRoot";
|
||||
ToolbarGroup.displayName = "ToolbarGroup";
|
||||
ToolbarItem.displayName = "ToolbarItem";
|
||||
ToolbarSeparator.displayName = "ToolbarSeparator";
|
||||
ToolbarSubmitButton.displayName = "ToolbarSubmitButton";
|
||||
|
||||
// compound components
|
||||
const Toolbar = Object.assign(ToolbarRoot, {
|
||||
Group: ToolbarGroup,
|
||||
Item: ToolbarItem,
|
||||
Separator: ToolbarSeparator,
|
||||
SubmitButton: ToolbarSubmitButton,
|
||||
});
|
||||
|
||||
export { Toolbar };
|
||||
|
|
@ -22,6 +22,7 @@ export default defineConfig({
|
|||
"src/table/index.ts",
|
||||
"src/tabs/index.ts",
|
||||
"src/toast/index.ts",
|
||||
"src/toolbar/index.ts",
|
||||
"src/tooltip/index.ts",
|
||||
"src/utils/index.ts",
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue