[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",
|
"./table": "./dist/table/index.js",
|
||||||
"./tabs": "./dist/tabs/index.js",
|
"./tabs": "./dist/tabs/index.js",
|
||||||
"./toast": "./dist/toast/index.js",
|
"./toast": "./dist/toast/index.js",
|
||||||
|
"./toolbar": "./dist/toolbar/index.js",
|
||||||
"./tooltip": "./dist/tooltip/index.js",
|
"./tooltip": "./dist/tooltip/index.js",
|
||||||
"./utils": "./dist/utils/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/table/index.ts",
|
||||||
"src/tabs/index.ts",
|
"src/tabs/index.ts",
|
||||||
"src/toast/index.ts",
|
"src/toast/index.ts",
|
||||||
|
"src/toolbar/index.ts",
|
||||||
"src/tooltip/index.ts",
|
"src/tooltip/index.ts",
|
||||||
"src/utils/index.ts",
|
"src/utils/index.ts",
|
||||||
],
|
],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue