diff --git a/src/frontend/packages/ui/package.json b/src/frontend/packages/ui/package.json index 123563e9..85e2ed9e 100644 --- a/src/frontend/packages/ui/package.json +++ b/src/frontend/packages/ui/package.json @@ -15,6 +15,8 @@ "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-toast": "^1.2.4", + "@radix-ui/react-toggle": "^1.1.1", + "@radix-ui/react-toggle-group": "^1.1.1", "@tanstack/react-table": "^8.20.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src/frontend/packages/ui/src/components/Toggle/index.ts b/src/frontend/packages/ui/src/components/Toggle/index.ts new file mode 100644 index 00000000..79873094 --- /dev/null +++ b/src/frontend/packages/ui/src/components/Toggle/index.ts @@ -0,0 +1,2 @@ +export { Toggle } from './toggle'; +export { ToggleGroup, ToggleGroupItem } from './toggle-group'; diff --git a/src/frontend/packages/ui/src/components/Toggle/toggle-group.stories.tsx b/src/frontend/packages/ui/src/components/Toggle/toggle-group.stories.tsx new file mode 100644 index 00000000..a9fc13e2 --- /dev/null +++ b/src/frontend/packages/ui/src/components/Toggle/toggle-group.stories.tsx @@ -0,0 +1,50 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ToggleGroup, ToggleGroupItem } from './toggle-group'; +import { useState } from 'react'; +import { Mic, MicOff, ScreenShare, ScreenShareOff, Video, VideoOff } from 'lucide-react'; + +const meta: Meta = { + title: 'Widget/ToggleGroup', + component: ToggleGroup, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +}; +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => { + const [isMicOn, setIsMicOn] = useState(false); + const [isVideoOn, setIsVideoOn] = useState(false); + const [isScreenSharing, setIsScreenSharing] = useState(false); + + return ( + + setIsMicOn((prev) => !prev)} + > + {isMicOn ? : } + + setIsVideoOn((prev) => !prev)} + > + {isVideoOn ? + setIsScreenSharing((prev) => !prev)} + > + {isScreenSharing ? : } + + + ); + }, +}; diff --git a/src/frontend/packages/ui/src/components/Toggle/toggle-group.tsx b/src/frontend/packages/ui/src/components/Toggle/toggle-group.tsx new file mode 100644 index 00000000..f958f474 --- /dev/null +++ b/src/frontend/packages/ui/src/components/Toggle/toggle-group.tsx @@ -0,0 +1,55 @@ +'use client'; + +import * as React from 'react'; +import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'; +import { type VariantProps } from 'class-variance-authority'; + +import { cn } from '@workspace/ui/lib/utils'; +import { toggleVariants } from './toggle'; + +const ToggleGroupContext = React.createContext>({ + size: 'default', + variant: 'outline', +}); + +const ToggleGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & VariantProps +>(({ className, variant, size, children, ...props }, ref) => ( + + {children} + +)); + +ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName; + +const ToggleGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & VariantProps +>(({ className, children, variant, size, ...props }, ref) => { + const context = React.useContext(ToggleGroupContext); + + return ( + + {children} + + ); +}); + +ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName; + +export { ToggleGroup, ToggleGroupItem }; diff --git a/src/frontend/packages/ui/src/components/Toggle/toggle.stories.tsx b/src/frontend/packages/ui/src/components/Toggle/toggle.stories.tsx new file mode 100644 index 00000000..6cc40faf --- /dev/null +++ b/src/frontend/packages/ui/src/components/Toggle/toggle.stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Toggle } from './toggle'; +import { Mic, MicOff } from 'lucide-react'; +import { useState } from 'react'; + +const meta: Meta = { + title: 'Widget/Toggle', + component: Toggle, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['default', 'outline'], + }, + size: { + control: 'radio', + options: ['default', 'lg'], + }, + }, +}; +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + variant: 'outline', + size: 'default', + }, + render: (args) => { + const [isPressed, setIsPressed] = useState(false); + return ( + + {isPressed ? : } + + ); + }, +}; diff --git a/src/frontend/packages/ui/src/components/Toggle/toggle.tsx b/src/frontend/packages/ui/src/components/Toggle/toggle.tsx new file mode 100644 index 00000000..c566f719 --- /dev/null +++ b/src/frontend/packages/ui/src/components/Toggle/toggle.tsx @@ -0,0 +1,40 @@ +'use client'; + +import * as React from 'react'; +import * as TogglePrimitive from '@radix-ui/react-toggle'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@workspace/ui/lib/utils'; + +const toggleVariants = cva( + 'inline-flex items-center justify-center rounded-full text-sm font-medium text-muted-foreground ring-offset-background transition-colors hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2', + { + variants: { + variant: { + default: 'bg-transparent', + outline: 'border border-input bg-transparent hover:text-accent-foreground', + }, + size: { + default: 'w-10 h-10 px-3', + lg: 'w-12 h-12 px-5', + }, + }, + defaultVariants: { + variant: 'outline', + size: 'default', + }, + }, +); + +const Toggle = React.forwardRef, React.ComponentPropsWithoutRef & VariantProps>( + ({ className, variant, size, ...props }, ref) => ( + + ), +); + +Toggle.displayName = TogglePrimitive.Root.displayName; + +export { Toggle, toggleVariants }; diff --git a/src/frontend/packages/ui/src/components/index.ts b/src/frontend/packages/ui/src/components/index.ts index 4e037a06..d288ee63 100644 --- a/src/frontend/packages/ui/src/components/index.ts +++ b/src/frontend/packages/ui/src/components/index.ts @@ -8,6 +8,7 @@ export * from './Toast'; export * from './Resizable'; export * from './Avatar'; export * from './Separate'; +export * from './Toggle'; export * from './Popover'; export * from './Label'; export * from './Input'; diff --git a/src/frontend/pnpm-lock.yaml b/src/frontend/pnpm-lock.yaml index 5240c46b..825c20d5 100644 --- a/src/frontend/pnpm-lock.yaml +++ b/src/frontend/pnpm-lock.yaml @@ -143,6 +143,12 @@ importers: '@radix-ui/react-toast': specifier: ^1.2.4 version: 1.2.4(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-toggle': + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-toggle-group': + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tanstack/react-table': specifier: ^8.20.6 version: 8.20.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -780,6 +786,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-direction@1.1.0': + resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dismissable-layer@1.1.3': resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} peerDependencies: @@ -915,6 +930,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-roving-focus@1.1.1': + resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-separator@1.1.1': resolution: {integrity: sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==} peerDependencies: @@ -950,6 +978,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toggle-group@1.1.1': + resolution: {integrity: sha512-OgDLZEA30Ylyz8YSXvnGqIHtERqnUt1KUYTKdw/y8u7Ci6zGiJfXc02jahmcSNK3YcErqioj/9flWC9S1ihfwg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.1': + resolution: {integrity: sha512-i77tcgObYr743IonC1hrsnnPmszDRn8p+EGUsUt+5a/JFn28fxaM88Py6V2mc8J5kELMWishI0rLnuGLFD/nnQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: @@ -4608,6 +4662,12 @@ snapshots: optionalDependencies: '@types/react': 19.0.7 + '@radix-ui/react-direction@1.1.0(@types/react@19.0.7)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + '@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -4737,6 +4797,23 @@ snapshots: '@types/react': 19.0.7 '@types/react-dom': 19.0.3(@types/react@19.0.7) + '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + '@radix-ui/react-separator@1.1.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -4773,6 +4850,32 @@ snapshots: '@types/react': 19.0.7 '@types/react-dom': 19.0.3(@types/react@19.0.7) + '@radix-ui/react-toggle-group@1.1.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-context': 1.1.1(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.0.7)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-toggle': 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + + '@radix-ui/react-toggle@1.1.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.3(@types/react@19.0.7))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.7 + '@types/react-dom': 19.0.3(@types/react@19.0.7) + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.0.7)(react@19.0.0)': dependencies: react: 19.0.0