This commit is contained in:
DCCONSTRUCTIONS
2026-04-18 18:39:25 +03:00
commit 3ba092b60c
4944 changed files with 497564 additions and 0 deletions
@@ -0,0 +1,7 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
export * from "./root";
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import * as React from "react";
import { Switch as BaseSwitch } from "@base-ui-components/react/switch";
import { cn } from "../utils/classname";
export interface IToggleSwitchProps {
value: boolean;
onChange: (value: boolean) => void;
label?: string;
size?: "sm" | "md" | "lg";
disabled?: boolean;
className?: string;
}
function Switch({ value, onChange, label, size = "sm", disabled, className }: IToggleSwitchProps) {
return (
<BaseSwitch.Root
checked={value}
disabled={disabled}
onCheckedChange={onChange}
aria-label={label}
className={cn(
"relative inline-flex flex-shrink-0 cursor-pointer rounded-full border border-subtle transition-colors duration-200 ease-in-out focus:outline-none",
// size
size === "sm" ? "h-4 w-6" : size === "md" ? "h-5 w-8" : "h-6 w-10",
// state
disabled
? "cursor-not-allowed bg-layer-1"
: value
? "cursor-pointer bg-accent-primary"
: "cursor-pointer bg-surface-2",
className
)}
>
{label && <span className="sr-only">{label}</span>}
<BaseSwitch.Thumb
aria-hidden="true"
className={cn(
"shadow inline-block self-center rounded-full ring-0 transition-transform duration-200 ease-in-out",
// size
size === "sm" ? "h-3 w-3" : size === "md" ? "h-4 w-4" : "h-5 w-5",
// position + color by state
value
? size === "sm"
? "translate-x-3 bg-white"
: size === "md"
? "translate-x-4 bg-white"
: "translate-x-5 bg-white"
: "translate-x-0.5 bg-surface-2",
// disabled
disabled && "cursor-not-allowed bg-surface-2"
)}
/>
</BaseSwitch.Root>
);
}
Switch.displayName = "plane-ui-switch";
export { Switch };
@@ -0,0 +1,247 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useState } from "react";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { useArgs } from "storybook/preview-api";
import { Switch } from "./root";
const meta = {
title: "Components/Switch",
component: Switch,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
args: { value: false, onChange: () => {} },
render(args) {
const [{ value }, updateArgs] = useArgs();
const setValue = (newValue: boolean) => updateArgs({ value: newValue });
return <Switch {...args} value={value} onChange={setValue} />;
},
} satisfies Meta<typeof Switch>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Checked: Story = {
args: { value: true },
};
export const WithLabel: Story = {
render(args) {
const [value, setValue] = useState(args.value);
return (
<div className="flex items-center gap-2">
<Switch {...args} value={value} onChange={setValue} label="Enable notifications" />
<label className="text-13">Enable notifications</label>
</div>
);
},
};
export const Small: Story = {
args: { size: "sm" },
};
export const Medium: Story = {
args: { size: "md" },
};
export const Large: Story = {
args: { size: "lg" },
};
export const Disabled: Story = {
args: { disabled: true },
};
export const DisabledChecked: Story = {
args: { value: true, disabled: true },
};
export const AllSizes: Story = {
render() {
const [small, setSmall] = useState(false);
const [medium, setMedium] = useState(false);
const [large, setLarge] = useState(false);
return (
<div className="flex items-center gap-6">
<div className="text-center">
<Switch value={small} onChange={setSmall} size="sm" />
<p className="text-gray-600 mt-2 text-11">Small</p>
</div>
<div className="text-center">
<Switch value={medium} onChange={setMedium} size="md" />
<p className="text-gray-600 mt-2 text-11">Medium</p>
</div>
<div className="text-center">
<Switch value={large} onChange={setLarge} size="lg" />
<p className="text-gray-600 mt-2 text-11">Large</p>
</div>
</div>
);
},
};
export const AllStates: Story = {
render() {
const [unchecked, setUnchecked] = useState(false);
const [checked, setChecked] = useState(true);
const [disabledUnchecked] = useState(false);
const [disabledChecked] = useState(true);
return (
<div className="space-y-4">
<div className="flex items-center gap-4">
<Switch value={unchecked} onChange={setUnchecked} />
<span className="text-gray-600 text-13">Unchecked</span>
</div>
<div className="flex items-center gap-4">
<Switch value={checked} onChange={setChecked} />
<span className="text-gray-600 text-13">Checked</span>
</div>
<div className="flex items-center gap-4">
<Switch value={disabledUnchecked} onChange={() => {}} disabled />
<span className="text-gray-600 text-13">Disabled Unchecked</span>
</div>
<div className="flex items-center gap-4">
<Switch value={disabledChecked} onChange={() => {}} disabled />
<span className="text-gray-600 text-13">Disabled Checked</span>
</div>
</div>
);
},
};
export const InForm: Story = {
render() {
const [notifications, setNotifications] = useState(true);
const [marketing, setMarketing] = useState(false);
const [updates, setUpdates] = useState(true);
return (
<div className="border-gray-200 shadow-md w-80 rounded-lg border bg-white p-6">
<h3 className="text-16 font-semibold">Notification Settings</h3>
<div className="mt-4 space-y-4">
<div className="flex items-center justify-between">
<div>
<p className="text-13 font-medium">Push Notifications</p>
<p className="text-gray-500 text-11">Receive push notifications on your device</p>
</div>
<Switch value={notifications} onChange={setNotifications} size="md" />
</div>
<div className="flex items-center justify-between">
<div>
<p className="text-13 font-medium">Marketing Emails</p>
<p className="text-gray-500 text-11">Receive emails about new features</p>
</div>
<Switch value={marketing} onChange={setMarketing} size="md" />
</div>
<div className="flex items-center justify-between">
<div>
<p className="text-13 font-medium">Product Updates</p>
<p className="text-gray-500 text-11">Get notified about product updates</p>
</div>
<Switch value={updates} onChange={setUpdates} size="md" />
</div>
</div>
</div>
);
},
};
export const WithDescription: Story = {
render() {
const [enabled, setEnabled] = useState(false);
return (
<div className="border-gray-200 w-96 rounded-lg border bg-white p-6">
<div className="flex items-start justify-between">
<div className="flex-1">
<h4 className="text-13 font-semibold">Enable Two-Factor Authentication</h4>
<p className="text-gray-500 mt-1 text-11">
Add an extra layer of security to your account by enabling two-factor authentication.
</p>
</div>
<Switch value={enabled} onChange={setEnabled} size="md" className="ml-4" />
</div>
</div>
);
},
};
export const Interactive: Story = {
render() {
const [enabled, setEnabled] = useState(false);
return (
<div className="border-gray-200 w-80 space-y-4 rounded-lg border bg-white p-6">
<div className="flex items-center justify-between">
<span className="text-13 font-medium">Feature Toggle</span>
<Switch value={enabled} onChange={setEnabled} size="md" />
</div>
<div className="bg-gray-50 rounded-sm p-4">
<p className="text-gray-700 text-13">
Status: <span className="font-semibold">{enabled ? "Enabled" : "Disabled"}</span>
</p>
{enabled && <p className="mt-2 text-11 text-success-primary">Feature is now active and ready to use!</p>}
</div>
</div>
);
},
};
export const CustomStyles: Story = {
render() {
const [value, setValue] = useState(false);
return (
<div className="flex items-center gap-4">
<Switch
value={value}
onChange={setValue}
size="lg"
className="border-purple-300 data-[state=checked]:bg-purple-500 border-2"
/>
<span className="text-13">Custom styled switch</span>
</div>
);
},
};
export const MultipleControls: Story = {
render() {
const [settings, setSettings] = useState({
feature1: true,
feature2: false,
feature3: true,
feature4: false,
feature5: true,
});
const toggleSetting = (key: keyof typeof settings) => {
setSettings((prev) => ({ ...prev, [key]: !prev[key] }));
};
return (
<div className="border-gray-200 w-96 rounded-lg border bg-white p-6">
<h3 className="mb-4 text-16 font-semibold">Feature Flags</h3>
<div className="space-y-3">
{Object.entries(settings).map(([key, value]) => (
<div key={key} className="flex items-center justify-between">
<span className="text-13 capitalize">{key.replace(/([A-Z])/g, " $1").trim()}</span>
<Switch value={value} onChange={() => toggleSetting(key as keyof typeof settings)} size="sm" />
</div>
))}
</div>
</div>
);
},
};