From 26d191ca8eb7e137f8247d2c686eeb1d7c4d72ef Mon Sep 17 00:00:00 2001 From: supakornnetsuwan Date: Thu, 23 Oct 2025 10:47:26 +0700 Subject: [PATCH 1/4] Migrate `input-otp`, `pagination`, `progress `, `select`, `switch` and `tabs` components - New combobox with input group example in `combobox-section` - New `input-otp` with examples - New `pagination `with examples - New `progress` with examples - New `select` with examples - New `switch` with examples - New `tabs` with examples - Improve playground sidebar, searching feature with persistent state --- .../playground/shadcn/combobox-section.tsx | 82 ++- .../playground/shadcn/input-otp-section.tsx | 363 ++++++++++ .../app/playground/shadcn/input-section.tsx | 4 +- .../app/playground/shadcn/page-sidebar.tsx | 101 +++ .../src/app/playground/shadcn/page.tsx | 146 +--- .../playground/shadcn/pagination-section.tsx | 489 ++++++++++++++ .../playground/shadcn/progress-section.tsx | 38 ++ .../app/playground/shadcn/select-section.tsx | 626 +++++++++++++++++ .../app/playground/shadcn/switch-section.tsx | 443 ++++++++++++ .../app/playground/shadcn/tabs-section.tsx | 636 ++++++++++++++++++ packages/react/package.json | 4 + .../react/components/primitives/input-otp.tsx | 22 + .../components/primitives/pagination.tsx | 12 + .../react/components/primitives/select.tsx | 37 + .../react/components/primitives/switch.tsx | 7 + .../src/react/components/primitives/tab.tsx | 33 + .../react/v2/components/primitives/index.ts | 6 + .../v2/components/primitives/input-group.tsx | 2 +- .../v2/components/primitives/input-otp.tsx | 76 +++ .../v2/components/primitives/pagination.tsx | 117 ++++ .../v2/components/primitives/progress.tsx | 29 + .../react/v2/components/primitives/select.tsx | 186 +++++ .../react/v2/components/primitives/switch.tsx | 29 + .../react/v2/components/primitives/tabs.tsx | 98 +++ .../react/v2/components/primitives/toggle.tsx | 2 +- pnpm-lock.yaml | 154 +++++ 26 files changed, 3624 insertions(+), 118 deletions(-) create mode 100644 examples/ui-playground/src/app/playground/shadcn/input-otp-section.tsx create mode 100644 examples/ui-playground/src/app/playground/shadcn/page-sidebar.tsx create mode 100644 examples/ui-playground/src/app/playground/shadcn/pagination-section.tsx create mode 100644 examples/ui-playground/src/app/playground/shadcn/progress-section.tsx create mode 100644 examples/ui-playground/src/app/playground/shadcn/select-section.tsx create mode 100644 examples/ui-playground/src/app/playground/shadcn/switch-section.tsx create mode 100644 examples/ui-playground/src/app/playground/shadcn/tabs-section.tsx create mode 100644 packages/react/v2/components/primitives/input-otp.tsx create mode 100644 packages/react/v2/components/primitives/pagination.tsx create mode 100644 packages/react/v2/components/primitives/progress.tsx create mode 100644 packages/react/v2/components/primitives/select.tsx create mode 100644 packages/react/v2/components/primitives/switch.tsx create mode 100644 packages/react/v2/components/primitives/tabs.tsx diff --git a/examples/ui-playground/src/app/playground/shadcn/combobox-section.tsx b/examples/ui-playground/src/app/playground/shadcn/combobox-section.tsx index f4f7f615..e4767a7a 100644 --- a/examples/ui-playground/src/app/playground/shadcn/combobox-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/combobox-section.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { CheckIcon } from '@phosphor-icons/react' +import { CheckIcon, UserIcon } from '@phosphor-icons/react' import { Button, @@ -13,6 +13,10 @@ import { ComboboxProvider, ComboboxTrigger, ComboboxTriggerMultiValue, + InputGroup, + InputGroupAddon, + InputGroupControl, + InputGroupText, Typography, } from '@genseki/react/v2' @@ -205,6 +209,70 @@ function ControlledComboboxSingle() { ) } +function ControlledInputGroupComboboxSingle() { + const [value, setValue] = React.useState([]) + const [open, setOpen] = React.useState(false) + + return ( +
+
+ + + + + + + + + +
+ {value || 'Please select item'} +
+
+
+ + + No language found. + + + {({ items }) => { + return items.map((language) => ( + + )) + }} + + + +
+
+
+
+ + Selected: {value.join(', ') || 'None'} + +
+ + +
+
+
+ ) +} + // Custom Trigger Combobox Example function CustomTriggerComboboxMultiple() { return ( @@ -326,6 +394,18 @@ export function ComboboxSection() { + + + A controlled combobox with input group. + +
+ +
+
+ A combobox with a custom trigger using a render prop. diff --git a/examples/ui-playground/src/app/playground/shadcn/input-otp-section.tsx b/examples/ui-playground/src/app/playground/shadcn/input-otp-section.tsx new file mode 100644 index 00000000..4c9ba7a1 --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/input-otp-section.tsx @@ -0,0 +1,363 @@ +'use client' + +import * as React from 'react' + +import { + InputOTP, + InputOTPGroup, + InputOTPSeparator, + InputOTPSlot, + Typography, +} from '@genseki/react/v2' + +import { PlaygroundCard } from '~/src/components/card' + +// Basic Input OTP +function BasicInputOtp() { + const [value, setValue] = React.useState('') + + return ( +
+ + + + + + + + + + + + + + + + + + Value: {value || 'Empty'} + +
+ ) +} + +// 4-digit OTP +function FourDigitOtp() { + const [value, setValue] = React.useState('') + + return ( +
+ + + + + + + + + + Value: {value || 'Empty'} + +
+ ) +} + +// 8-digit OTP with separators +function EightDigitOtp() { + const [value, setValue] = React.useState('') + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + Value: {value || 'Empty'} + +
+ ) +} + +// Disabled OTP +function DisabledOtp() { + return ( +
+ + + + + + + + + + + + + + + + + + This OTP input is disabled and cannot be interacted with. + +
+ ) +} + +// Invalid OTP +function InvalidOtp() { + const [value, setValue] = React.useState('') + + return ( +
+ + + + + + + + + + + + + + + + + + This OTP input has an invalid state with error styling. + +
+ ) +} + +// OTP with validation +function ValidatedOtp() { + const [value, setValue] = React.useState('') + const [isValid, setIsValid] = React.useState(null) + + React.useEffect(() => { + if (value.length === 6) { + // Simulate validation - in real app, this would be an API call + const isValidCode = value === '123456' + setIsValid(isValidCode) + } else { + setIsValid(null) + } + }, [value]) + + return ( +
+ + + + + + + + + + + + + + + + +
+ + Value: {value || 'Empty'} + + {isValid === true && ( + + ✓ Valid code! + + )} + {isValid === false && ( + + ✗ Invalid code. Try 123456 + + )} +
+
+ ) +} + +// OTP with custom separator +function CustomSeparatorOtp() { + const [value, setValue] = React.useState('') + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + Value: {value || 'Empty'} + +
+ ) +} + +// OTP with different sizes +function DifferentSizesOtp() { + const [value1, setValue1] = React.useState('') + const [value2, setValue2] = React.useState('') + + return ( +
+
+ + Small Size + + + + + + + + + +
+ +
+ + Default Size + + + + + + + + + +
+ + + Small: {value1 || 'Empty'} | Default: {value2 || 'Empty'} + +
+ ) +} + +export function InputOtpSection() { + return ( +
+ + + A 6-digit OTP input with separators and value tracking. + +
+ +
+
+ + + + A simple 4-digit OTP input without separators. + +
+ +
+
+ + + + An 8-digit OTP input with multiple separators for longer codes. + +
+ +
+
+ + + + Disabled OTP input that cannot be interacted with. + +
+ +
+
+ + + + OTP input in invalid state with error styling. + +
+ +
+
+ + + + OTP input with real-time validation. Try entering 123456 for a valid code. + +
+ +
+
+ + + + OTP input with custom bullet point separators instead of dashes. + +
+ +
+
+ + + + OTP inputs in different sizes: small and default. + +
+ +
+
+
+ ) +} diff --git a/examples/ui-playground/src/app/playground/shadcn/input-section.tsx b/examples/ui-playground/src/app/playground/shadcn/input-section.tsx index 0ed39176..e44e37dd 100644 --- a/examples/ui-playground/src/app/playground/shadcn/input-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/input-section.tsx @@ -104,10 +104,10 @@ function InputWithLabels() {
-
-
-
-
- - {'>'} Button - -
- - {'>'} Combobox - -
- - {'>'} Date picker - -
- - {'>'} Input - -
- - {'>'} Link - -
- - {'>'} Tooltip - -
- - {'>'} Dropdown Menu - -
- - {'>'} Collapsible - -
- - {'>'} Toggle - -
- - {'>'} Toggle Group - -
- - {'>'} Textarea - -
- - {'>'} Toast - -
- - {'>'} Sidebar - -
-
-
+ ) } diff --git a/examples/ui-playground/src/app/playground/shadcn/pagination-section.tsx b/examples/ui-playground/src/app/playground/shadcn/pagination-section.tsx new file mode 100644 index 00000000..5b309e53 --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/pagination-section.tsx @@ -0,0 +1,489 @@ +'use client' + +import * as React from 'react' + +import { CaretDoubleLeftIcon, CaretDoubleRightIcon } from '@phosphor-icons/react' + +import { Button, buttonVariants, Typography } from '@genseki/react/v2' +import { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from '@genseki/react/v2' + +import { PlaygroundCard } from '~/src/components/card' + +import { cn } from '../../../../../../packages/react/src/react/utils/cn' + +// Default Pagination +function DefaultPagination() { + const [currentPage, setCurrentPage] = React.useState(1) + const totalPages = 10 + + return ( +
+ + + + { + e.preventDefault() + setCurrentPage(Math.max(1, currentPage - 1)) + }} + className={currentPage === 1 ? 'pointer-events-none opacity-50' : ''} + /> + + {[...Array(3).keys()].map((i) => { + const page = i + 1 + return ( + + { + e.preventDefault() + setCurrentPage(page) + }} + isActive={currentPage === page} + > + {page} + + + ) + })} + + { + e.preventDefault() + setCurrentPage(Math.min(totalPages, currentPage + 1)) + }} + className={currentPage === totalPages ? 'pointer-events-none opacity-50' : ''} + /> + + + + + Current page: {currentPage} of {totalPages} + +
+ ) +} + +function CustomizedPagination() { + const [currentPage, setCurrentPage] = React.useState(2) + const totalPages = 10 + + return ( +
+ + + {[...Array(12).keys()].map((i) => { + const page = i + 1 + const isActive = currentPage === page + return ( + + { + e.preventDefault() + setCurrentPage(page) + }} + isActive={currentPage === page} + className={cn( + 'gap-2', + isActive && 'border-none', + buttonVariants({ + variant: isActive ? 'primary' : 'outline', + size: 'md', + }) + )} + > + {page} + + + ) + })} + + + + Current page: {currentPage} of {totalPages} + +
+ ) +} + +// First and Last Page Buttons +function FirstLastPagination() { + const [currentPage, setCurrentPage] = React.useState(2) + const totalPages = 10 + + return ( +
+ + + + + + {[...Array(3).keys()].map((i) => { + const page = i + 1 + + return ( + + { + e.preventDefault() + setCurrentPage(page) + }} + isActive={currentPage === page} + > + {page} + + + ) + })} + + + + + + + Current page: {currentPage} of {totalPages} + +
+ ) +} + +// Bordered Pagination +function BorderedPagination() { + const [currentPage, setCurrentPage] = React.useState(2) + const totalPages = 10 + + return ( +
+ + + + { + e.preventDefault() + setCurrentPage(Math.max(1, currentPage - 1)) + }} + className={currentPage === 1 ? 'pointer-events-none opacity-50' : ''} + /> + + {Array.from({ length: 3 }, (_, i) => { + const page = i + 1 + return ( + + { + e.preventDefault() + setCurrentPage(page) + }} + isActive={currentPage === page} + > + {page} + + + ) + })} + + { + e.preventDefault() + setCurrentPage(Math.min(totalPages, currentPage + 1)) + }} + className={currentPage === totalPages ? 'pointer-events-none opacity-50' : ''} + /> + + + + + Current page: {currentPage} of {totalPages} + +
+ ) +} + +// With Ellipsis Pagination +function WithEllipsisPagination() { + const [currentPage, setCurrentPage] = React.useState(5) + const totalPages = 20 + + const getVisiblePages = () => { + if (currentPage <= 3) return [1, 2, 3, 4, 5] + if (currentPage >= totalPages - 2) + return [totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages] + return [currentPage - 2, currentPage - 1, currentPage, currentPage + 1, currentPage + 2] + } + + const visiblePages = getVisiblePages() + const showStartEllipsis = currentPage > 4 + const showEndEllipsis = currentPage < totalPages - 3 + + return ( +
+ + + + { + e.preventDefault() + setCurrentPage(Math.max(1, currentPage - 1)) + }} + className={currentPage === 1 ? 'pointer-events-none opacity-50' : ''} + /> + + + {showStartEllipsis && ( + <> + + { + e.preventDefault() + setCurrentPage(1) + }} + isActive={currentPage === 1} + > + 1 + + + + + + + )} + + {visiblePages.map((page) => ( + + { + e.preventDefault() + setCurrentPage(page) + }} + isActive={currentPage === page} + > + {page} + + + ))} + + {showEndEllipsis && ( + <> + + + + + { + e.preventDefault() + setCurrentPage(totalPages) + }} + isActive={currentPage === totalPages} + > + {totalPages} + + + + )} + + + { + e.preventDefault() + setCurrentPage(Math.min(totalPages, currentPage + 1)) + }} + className={currentPage === totalPages ? 'pointer-events-none opacity-50' : ''} + /> + + + + + Current page: {currentPage} of {totalPages} + +
+ ) +} + +// Numberless Pagination +function NumberlessPagination() { + const [currentPage, setCurrentPage] = React.useState(2) + const totalPages = 10 + + return ( +
+ + + + { + e.preventDefault() + setCurrentPage(Math.max(1, currentPage - 1)) + }} + className={currentPage === 1 ? 'pointer-events-none opacity-50' : ''} + /> + + + { + e.preventDefault() + setCurrentPage(Math.min(totalPages, currentPage + 1)) + }} + className={currentPage === totalPages ? 'pointer-events-none opacity-50' : ''} + /> + + + + + Current page: {currentPage} of {totalPages} + +
+ ) +} + +// Numberless with Text +function NumberlessWithTextPagination() { + const [currentPage, setCurrentPage] = React.useState(1) + const totalPages = 21 + + return ( +
+ + + + { + e.preventDefault() + setCurrentPage(Math.max(1, currentPage - 1)) + }} + className={currentPage === 1 ? 'pointer-events-none opacity-50' : ''} + /> + + + + Page {currentPage} of {totalPages} + + + + { + e.preventDefault() + setCurrentPage(Math.min(totalPages, currentPage + 1)) + }} + className={currentPage === totalPages ? 'pointer-events-none opacity-50' : ''} + /> + + + + + Current page: {currentPage} of {totalPages} + +
+ ) +} + +export function PaginationSection() { + return ( +
+ + + Basic pagination with previous/next buttons and page numbers. + +
+ +
+
+ + + + Pagination with custom pagination link. + +
+ +
+
+ + + + Pagination with first and last page navigation buttons. + +
+ +
+
+ + + + Pagination with a border around the entire component. + +
+ +
+
+ + + + Pagination with ellipsis for large page counts, showing relevant pages around current + page. + +
+ +
+
+ + + + Simple pagination with only previous and next buttons, no page numbers. + +
+ +
+
+ + + + Pagination with previous/next buttons and current page information text. + +
+ +
+
+
+ ) +} diff --git a/examples/ui-playground/src/app/playground/shadcn/progress-section.tsx b/examples/ui-playground/src/app/playground/shadcn/progress-section.tsx new file mode 100644 index 00000000..74905b97 --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/progress-section.tsx @@ -0,0 +1,38 @@ +'use client' +import { useState } from 'react' + +import { Button, Progress, Typography } from '@genseki/react/v2' + +import { PlaygroundCard } from '../../../components/card' + +function DefaultProgress() { + const [progress, setProgress] = useState(50) + + return ( +
+ + +
+ ) +} + +export function ProgressSection() { + return ( +
+ + + Basic progress bar + +
+ +
+
+
+ ) +} diff --git a/examples/ui-playground/src/app/playground/shadcn/select-section.tsx b/examples/ui-playground/src/app/playground/shadcn/select-section.tsx new file mode 100644 index 00000000..69b64ce0 --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/select-section.tsx @@ -0,0 +1,626 @@ +'use client' + +import * as React from 'react' + +import { + CircleIcon, + HexagonIcon, + SquareIcon, + TriangleIcon, + UserIcon, + XIcon, +} from '@phosphor-icons/react' + +import { + InputGroup, + InputGroupAddon, + InputGroupButton, + InputGroupControl, + InputGroupText, + SelectItemIndicator, + SelectItemText, + Typography, +} from '@genseki/react/v2' +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} from '@genseki/react/v2' + +import { PlaygroundCard } from '~/src/components/card' + +// Basic Select +function BasicSelect() { + const [value, setValue] = React.useState('') + + return ( +
+ + + + Selected value: {value || 'None'} + +
+ ) +} + +// Input group select +function InputGroupSelect() { + const [value, setValue] = React.useState('') + + return ( +
+ + + + + + + + + + + + + + + + Selected value: {value || 'None'} + +
+ ) +} + +// Input group with icon value +function InputGroupValueWithIcon() { + const [value, setValue] = React.useState('apple') + + return ( +
+ + + + Selected value: {value || 'None'} + +
+ ) +} + +// Basic Select with leading icon +function BasicSelectLeadingIcon() { + const [value, setValue] = React.useState('grapes') + + return ( +
+ + + Selected value: {value || 'None'} + +
+ ) +} + +// Select item with icon +function SelectItemIcon() { + const [value, setValue] = React.useState('mango') + + return ( +
+ + + Selected value: {value || 'None'} + +
+ ) +} + +// Select with Groups +function SelectWithGroups() { + const [value, setValue] = React.useState('') + + return ( +
+ + + Selected value: {value || 'None'} + +
+ ) +} + +// Select Sizes +function SelectSizes() { + const [smallValue, setSmallValue] = React.useState('') + const [defaultValue, setDefaultValue] = React.useState('') + + return ( +
+
+
+ + Small Size + + +
+
+ + Default Size + + +
+
+ + Small: {smallValue || 'None'} | Default: {defaultValue || 'None'} + +
+ ) +} + +// Disabled Select +function DisabledSelect() { + return ( +
+ + + This select is disabled and cannot be interacted with. + +
+ ) +} + +// Long List with Scroll +function LongListSelect() { + const [value, setValue] = React.useState('') + + return ( +
+ + + Selected country: {value || 'None'} + +
+ ) +} + +// Invalid Select +function InvalidSelect() { + const [value, setValue] = React.useState('') + + return ( +
+ + + + + This select has an invalid state with error styling. + +
+ ) +} + +export function SelectSection() { + return ( +
+ + + A simple select dropdown with placeholder text and value tracking. + +
+ +
+
+ + + A simple select dropdown with input group. + +
+ +
+
+ + + A simple select dropdown with icon value. + +
+ +
+
+ + + A simple select dropdown with placeholder text and value tracking with leading icon. + +
+ +
+
+ + + + A simple select dropdown with items icon + +
+ +
+
+ + + + Select dropdown with grouped options and separators for better organization. + +
+ +
+
+ + + + Select components in different sizes: small and default. + +
+ +
+
+ + + + Disabled select component that cannot be interacted with. + +
+ +
+
+ + + + Select component in invalid state with error styling and accessibility attributes. + +
+ +
+
+ + + + Select with many options demonstrating scroll functionality and scroll buttons. + +
+ +
+
+
+ ) +} diff --git a/examples/ui-playground/src/app/playground/shadcn/switch-section.tsx b/examples/ui-playground/src/app/playground/shadcn/switch-section.tsx new file mode 100644 index 00000000..f54898ae --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/switch-section.tsx @@ -0,0 +1,443 @@ +'use client' + +import * as React from 'react' + +import { BellIcon, MoonIcon, ShieldIcon, WifiHighIcon } from '@phosphor-icons/react' + +import { Label, Typography } from '@genseki/react/v2' +import { Switch } from '@genseki/react/v2' + +import { PlaygroundCard } from '~/src/components/card' + +// Basic Switch +function BasicSwitch() { + const [isEnabled, setIsEnabled] = React.useState(false) + + return ( +
+
+ + +
+ + Switch is {isEnabled ? 'enabled' : 'disabled'} + +
+ ) +} + +// Switch with Label +function SwitchWithLabel() { + const [notifications, setNotifications] = React.useState(true) + const [darkMode, setDarkMode] = React.useState(false) + const [autoSave, setAutoSave] = React.useState(true) + + return ( +
+
+
+
+ + + Receive notifications about new messages and updates. + +
+ +
+ +
+
+ + + Switch between light and dark themes. + +
+ +
+ +
+
+ + + Automatically save your work as you type. + +
+ +
+
+
+ ) +} + +// Switch with Icons +function SwitchWithIcons() { + const [darkMode, setDarkMode] = React.useState(false) + const [notifications, setNotifications] = React.useState(true) + const [wifi, setWifi] = React.useState(true) + const [security, setSecurity] = React.useState(false) + + return ( +
+
+
+
+ +
+ + + Toggle dark theme + +
+
+ +
+ +
+
+ +
+ + + Get notified about updates + +
+
+ +
+ +
+
+ +
+ + + Connect to wireless networks + +
+
+ +
+ +
+
+ +
+ + + Enable security features + +
+
+ +
+
+
+ ) +} + +// Disabled Switch +function DisabledSwitch() { + return ( +
+
+
+
+ + + This switch is disabled and turned on. + +
+ +
+ +
+
+ + + This switch is disabled and turned off. + +
+ +
+
+
+ ) +} + +// Switch Sizes +function SwitchSizes() { + const [small, setSmall] = React.useState(false) + const [defaultSize, setDefaultSize] = React.useState(true) + const [large, setLarge] = React.useState(false) + + return ( +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ ) +} + +// Switch with Form +function SwitchWithForm() { + const [formData, setFormData] = React.useState({ + emailNotifications: true, + smsNotifications: false, + marketingEmails: false, + securityAlerts: true, + weeklyDigest: true, + autoBackup: false, + }) + + const handleSwitchChange = (key: keyof typeof formData) => (checked: boolean) => { + setFormData((prev) => ({ ...prev, [key]: checked })) + } + + return ( +
+
+ + Notification Preferences + + +
+
+
+ + + Receive notifications via email + +
+ +
+ +
+
+ + + Receive notifications via SMS + +
+ +
+ +
+
+ + + Receive promotional and marketing content + +
+ +
+ +
+
+ + + Get notified about security-related events + +
+ +
+ +
+
+ + + Receive a weekly summary of activity + +
+ +
+ +
+
+ + + Automatically backup your data + +
+ +
+
+
+ +
+ + Current Settings:{' '} + {Object.entries(formData) + .filter(([_, value]) => value) + .map(([key, _]) => key.replace(/([A-Z])/g, ' $1').toLowerCase()) + .join(', ') || 'None enabled'} + +
+
+ ) +} + +// Switch with Custom Styling +function CustomStyledSwitch() { + const [customSwitch, setCustomSwitch] = React.useState(false) + + return ( +
+
+
+
+ + + Switch with custom colors and styling + +
+ +
+ +
+
+ + + Switch with gradient background + +
+ +
+
+
+ ) +} + +export function SwitchSection() { + return ( +
+ + + A simple switch component with basic functionality and state tracking. + +
+ +
+
+ + + + Switches with descriptive labels and helper text for better user experience. + +
+ +
+
+ + + + Switches enhanced with icons to improve visual recognition and user experience. + +
+ +
+
+ + + + Disabled switches in both on and off states to prevent user interaction. + +
+ +
+
+ + + + Switches in different sizes: small, default, and large. + +
+ +
+
+ + + + Switches integrated into a form with multiple options and state management. + +
+ +
+
+ + + + Switches with custom styling and colors for different visual themes. + +
+ +
+
+
+ ) +} diff --git a/examples/ui-playground/src/app/playground/shadcn/tabs-section.tsx b/examples/ui-playground/src/app/playground/shadcn/tabs-section.tsx new file mode 100644 index 00000000..9901ece9 --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/tabs-section.tsx @@ -0,0 +1,636 @@ +'use client' + +import * as React from 'react' + +import { BellIcon, GearIcon, HeartIcon, StarIcon, UserIcon } from '@phosphor-icons/react' + +import { Label, Typography } from '@genseki/react/v2' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@genseki/react/v2' + +import { PlaygroundCard } from '~/src/components/card' + +// Basic Tabs +function BasicTabs() { + return ( +
+ + + Account + Password + Settings + + +
+ + Account Information + + + Manage your account settings and preferences here. + +
+
+ +
+ + Password Settings + + + Update your password and security settings. + +
+
+ +
+ + General Settings + + + Configure your general application preferences. + +
+
+
+
+ ) +} + +// Basic Tabs +function BasicUnderlineTabs() { + return ( +
+ + + Account + Password + Settings + + +
+ + Account Information + + + Manage your account settings and preferences here. + +
+
+ +
+ + Password Settings + + + Update your password and security settings. + +
+
+ +
+ + General Settings + + + Configure your general application preferences. + +
+
+
+
+ ) +} + +// Tabs with Icons +function TabsWithIcons() { + return ( +
+ + + + + Profile + + + + Settings + + + + Notifications + + + +
+ + User Profile + + + View and edit your personal information and profile details. + +
+
+ +
+ + Account Settings + + + Configure your account preferences and privacy settings. + +
+
+ +
+ + Notification Preferences + + + Manage how and when you receive notifications. + +
+
+
+
+ ) +} +// Tabs with Icons +function UnderlineTabsWithIcons() { + return ( +
+ + + + + Profile + + + + Settings + + + + Notifications + + + +
+ + User Profile + + + View and edit your personal information and profile details. + +
+
+ +
+ + Account Settings + + + Configure your account preferences and privacy settings. + +
+
+ +
+ + Notification Preferences + + + Manage how and when you receive notifications. + +
+
+
+
+ ) +} + +// Vertical Tabs +function VerticalTabs() { + return ( +
+ + + + Overview + + + Analytics + + + Reports + + + Security + + + +
+ + Dashboard Overview + + + Get a comprehensive view of your dashboard metrics and key performance indicators. + +
+
+ +
+ + Analytics Dashboard + + + Detailed analytics and insights about your data and user behavior. + +
+
+ +
+ + Reports & Exports + + + Generate and download various reports for your business needs. + +
+
+ +
+ + Security Center + + + Monitor security events and configure security settings. + +
+
+
+
+ ) +} + +// Disabled Tabs +function DisabledTabs() { + return ( +
+ + + Active Tab + + Disabled Tab + + Another Tab + + +
+ + Active Content + + + This tab is active and fully functional. + +
+
+ +
+ + Disabled Content + + + This content is associated with a disabled tab. + +
+
+ +
+ + Another Active Tab + + + This is another active tab with content. + +
+
+
+
+ ) +} + +// Tabs with Badge +function TabsWithBadge() { + return ( +
+ + + + Messages +
+ 3 +
+
+ + + Favorites + + 12 + + + + + Starred + +
+ +
+ + Messages + + + You have 3 unread messages in your inbox. + +
+
+ +
+ + Favorite Items + + + You have 12 items marked as favorites. + +
+
+ +
+ + Starred Content + + + View all your starred items and important content. + +
+
+
+
+ ) +} + +// Tabs with Form Content +function TabsWithForm() { + const [formData, setFormData] = React.useState({ + name: '', + email: '', + bio: '', + }) + + return ( +
+ + + Personal Info + Contact Details + Preferences + + +
+ + Personal Information + +
+ + setFormData({ ...formData, name: e.target.value })} + className="w-full p-2 border rounded-md" + placeholder="Enter your name" + /> +
+
+ +