From 4662460399e11735302a229da8ee3dc3951057c5 Mon Sep 17 00:00:00 2001 From: supakornnetsuwan Date: Thu, 16 Oct 2025 17:13:45 +0700 Subject: [PATCH 1/4] migrate: introduce to new sidebar --- .../src/app/playground/shadcn/page.tsx | 42 +- .../shadcn/sidebar/components/app-sidebar.tsx | 130 ++++ .../shadcn/sidebar/components/nav-header.tsx | 36 + .../shadcn/sidebar/components/nav-main.tsx | 72 ++ .../app/playground/shadcn/sidebar/page.tsx | 53 ++ packages/react/package.json | 2 + .../components/primitives/breadcrumbs.tsx | 24 + .../react/components/primitives/sidebar.tsx | 132 +++- .../react/components/primitives/tooltip.tsx | 19 + .../v2/components/primitives/breadcrumb.tsx | 103 +++ .../v2/components/primitives/collapsible.tsx | 21 + .../react/v2/components/primitives/index.ts | 5 + .../v2/components/primitives/sidebar.tsx | 689 ++++++++++++++++++ .../v2/components/primitives/skeleton.tsx | 13 + .../v2/components/primitives/tooltip.tsx | 58 ++ pnpm-lock.yaml | 90 +++ 16 files changed, 1478 insertions(+), 11 deletions(-) create mode 100644 examples/ui-playground/src/app/playground/shadcn/sidebar/components/app-sidebar.tsx create mode 100644 examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-header.tsx create mode 100644 examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-main.tsx create mode 100644 examples/ui-playground/src/app/playground/shadcn/sidebar/page.tsx create mode 100644 packages/react/v2/components/primitives/breadcrumb.tsx create mode 100644 packages/react/v2/components/primitives/collapsible.tsx create mode 100644 packages/react/v2/components/primitives/sidebar.tsx create mode 100644 packages/react/v2/components/primitives/skeleton.tsx create mode 100644 packages/react/v2/components/primitives/tooltip.tsx diff --git a/examples/ui-playground/src/app/playground/shadcn/page.tsx b/examples/ui-playground/src/app/playground/shadcn/page.tsx index 27dcfeb3..e3407e49 100644 --- a/examples/ui-playground/src/app/playground/shadcn/page.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/page.tsx @@ -1,9 +1,9 @@ -'use client' import * as React from 'react' import Link from 'next/link' import { Typography } from '@genseki/react' +import { linkVariants } from '@genseki/react/v2' import { ButtonSection } from './button-section' import { ComboboxSection } from './combobox-section' @@ -40,14 +40,44 @@ export default function ComboboxPage() {
-
- {'>'} Button +
+ + {'>'} Button +
- {'>'} Combobox + + {'>'} Combobox +
- {'>'} Input + + {'>'} Input +
- {'>'} Link + + {'>'} Link + +
+ + {'>'} Sidebar +
diff --git a/examples/ui-playground/src/app/playground/shadcn/sidebar/components/app-sidebar.tsx b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/app-sidebar.tsx new file mode 100644 index 00000000..280feb8c --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/app-sidebar.tsx @@ -0,0 +1,130 @@ +import * as React from 'react' + +import { + BookOpenIcon, + RobotIcon, + StackIcon, + TerminalWindowIcon, + WaveformIcon, +} from '@phosphor-icons/react' + +import { Typography } from '@genseki/react' +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarRail, +} from '@genseki/react/v2' + +import { NavHeader } from './nav-header' +import { NavMain } from './nav-main' + +// This is sample data. +const data = { + user: { + name: 'shadcn', + email: 'm@example.com', + avatar: '/avatars/shadcn.jpg', + }, + teams: [ + { + name: 'Acme Inc', + logo: StackIcon, + plan: 'Enterprise', + }, + { + name: 'Acme Corp.', + logo: WaveformIcon, + plan: 'Startup', + }, + { + name: 'Evil Corp.', + logo: TerminalWindowIcon, + plan: 'Free', + }, + ], + navMain: [ + { + title: 'Playground', + url: '#', + icon: TerminalWindowIcon, + isActive: true, + items: [ + { + title: 'History', + url: '#', + }, + { + title: 'Starred', + url: '#', + }, + { + title: 'Settings', + url: '#', + }, + ], + }, + { + title: 'Models', + url: '#', + icon: RobotIcon, + items: [ + { + title: 'Genesis', + url: '#', + }, + { + title: 'Explorer', + url: '#', + }, + { + title: 'Quantum', + url: '#', + }, + ], + }, + { + title: 'Documentation', + url: '#', + icon: BookOpenIcon, + items: [ + { + title: 'Introduction', + url: '#', + }, + { + title: 'Get Started', + url: '#', + }, + { + title: 'Tutorials', + url: '#', + }, + { + title: 'Changelog', + url: '#', + }, + ], + }, + ], +} + +export function AppSidebar({ ...props }: React.ComponentProps) { + return ( + + + + + + + + +
+ Footer content +
+
+ +
+ ) +} diff --git a/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-header.tsx b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-header.tsx new file mode 100644 index 00000000..a59d1890 --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-header.tsx @@ -0,0 +1,36 @@ +import { Typography } from '@genseki/react' +import { SidebarMenu, SidebarMenuItem, useSidebar } from '@genseki/react/v2' + +function NavHeader() { + const ctx = useSidebar() + + return ( + + + {/*
+
+
+
+ +
+
+ + {props.title} + + + {props.version} + +
+
*/} + +
+ + BRV + +
+ + + ) +} + +export { NavHeader } diff --git a/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-main.tsx b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-main.tsx new file mode 100644 index 00000000..69ab9047 --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-main.tsx @@ -0,0 +1,72 @@ +'use client' + +import type { Icon } from '@phosphor-icons/react' +import { CaretRightIcon } from '@phosphor-icons/react' + +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, + SidebarGroup, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSub, + SidebarMenuSubButton, + SidebarMenuSubItem, +} from '@genseki/react/v2' + +function NavMain({ + items, +}: { + items: { + title: string + url: string + icon?: Icon + isActive?: boolean + items?: { + title: string + url: string + }[] + }[] +}) { + return ( + + + {items.map((item) => ( + + + + + {item.icon && } + {item.title} + + + + + + {item.items?.map((subItem) => ( + + + + {subItem.title} + + + + ))} + + + + + ))} + + + ) +} + +export { NavMain } diff --git a/examples/ui-playground/src/app/playground/shadcn/sidebar/page.tsx b/examples/ui-playground/src/app/playground/shadcn/sidebar/page.tsx new file mode 100644 index 00000000..683fa631 --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/sidebar/page.tsx @@ -0,0 +1,53 @@ +'use client' + +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, + Separator, + SidebarInset, + SidebarProvider, + SidebarTrigger, +} from '@genseki/react/v2' + +import { AppSidebar } from './components/app-sidebar' + +function DummySidebarPage() { + return ( + + + +
+
+ + + + + + Building Your Application + + + + Data Fetching + + + +
+
+
+
+
+
+
+
+
+
+ + + ) +} + +export default DummySidebarPage diff --git a/packages/react/package.json b/packages/react/package.json index 967c24df..03697421 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -52,11 +52,13 @@ "@intentui/icons": "^1.10.31", "@internationalized/date": "^3.8.2", "@phosphor-icons/react": "^2.1.8", + "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-use-controllable-state": "^1.2.2", "@react-aria/i18n": "^3.12.9", "@react-aria/visually-hidden": "^3.8.23", diff --git a/packages/react/src/react/components/primitives/breadcrumbs.tsx b/packages/react/src/react/components/primitives/breadcrumbs.tsx index 50b5a425..71c61b71 100644 --- a/packages/react/src/react/components/primitives/breadcrumbs.tsx +++ b/packages/react/src/react/components/primitives/breadcrumbs.tsx @@ -9,14 +9,29 @@ import { twMerge } from 'tailwind-merge' import { Link } from './link' import { composeTailwindRenderProps } from './primitive' +/** + * + * React Aria component + * + */ + +/** + * @deprecated + */ interface BreadcrumbsContextProps { separator?: 'chevron' | 'slash' | boolean } +/** + * @deprecated + */ const BreadcrumbsProvider = createContext({ separator: 'slash', }) +/** + * @deprecated + */ const Breadcrumbs = ({ className, ...props @@ -28,11 +43,17 @@ const Breadcrumbs = ({ ) } +/** + * @deprecated + */ interface BreadcrumbsItemProps extends BreadcrumbProps, BreadcrumbsContextProps { href?: string trailing?: React.ReactNode } +/** + * @deprecated + */ const BreadcrumbsItem = ({ href, separator = true, @@ -63,6 +84,9 @@ const BreadcrumbsItem = ({ ) } +/** + * @deprecated + */ const Separator = ({ separator = 'slash' }: { separator?: BreadcrumbsItemProps['separator'] }) => { return ( diff --git a/packages/react/src/react/components/primitives/sidebar.tsx b/packages/react/src/react/components/primitives/sidebar.tsx index a2b2713f..8073d707 100644 --- a/packages/react/src/react/components/primitives/sidebar.tsx +++ b/packages/react/src/react/components/primitives/sidebar.tsx @@ -34,9 +34,17 @@ import { Tooltip, TooltipContent } from './tooltip' import { BaseIcon } from '../../components/primitives/base-icon' import { useMediaQuery } from '../../hooks/use-media-query' - +/** + * @deprecated + */ const SIDEBAR_COOKIE_NAME = 'sidebar-state' +/** + * @deprecated + */ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 +/** + * @deprecated + */ type SidebarContextProps = { state: 'expanded' | 'collapsed' open: boolean @@ -47,8 +55,14 @@ type SidebarContextProps = { toggleSidebar: () => void } +/** + * @deprecated + */ const SidebarContext = createContext(null) +/** + * @deprecated + */ const useSidebar = () => { const context = use(SidebarContext) if (!context) { @@ -58,6 +72,9 @@ const useSidebar = () => { return context } +/** + * @deprecated + */ interface SidebarProviderProps extends React.ComponentProps<'div'> { defaultOpen?: boolean isOpen?: boolean @@ -65,6 +82,9 @@ interface SidebarProviderProps extends React.ComponentProps<'div'> { onOpenChange?: (open: boolean) => void } +/** + * @deprecated + */ const SidebarProvider = ({ defaultOpen = true, isOpen: openProp, @@ -149,6 +169,9 @@ const SidebarProvider = ({ ) } +/** + * @deprecated + */ const gap = tv({ base: [ 'w-(--sidebar-width) group-data-[sidebar-collapsible=hidden]/sidebar-container:w-0', @@ -167,6 +190,9 @@ const gap = tv({ }, }) +/** + * @deprecated + */ const sidebar = tv({ base: [ 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) not-has-data-sidebar-footer:pb-2 transition-[left,right,width] duration-200 ease-linear md:flex', @@ -198,6 +224,9 @@ const sidebar = tv({ }, }) +/** + * @deprecated + */ interface SidebarProps extends React.ComponentProps<'div'> { intent?: 'default' | 'float' | 'inset' | 'fleet' collapsible?: 'hidden' | 'dock' | 'none' @@ -205,6 +234,9 @@ interface SidebarProps extends React.ComponentProps<'div'> { closeButton?: boolean } +/** + * @deprecated + */ const Sidebar = ({ closeButton = true, collapsible = 'hidden', @@ -281,6 +313,9 @@ const Sidebar = ({ ) } +/** + * @deprecated + */ const header = tv({ base: 'mb-2 flex flex-col **:data-[slot=sidebar-label-mask]:hidden', variants: { @@ -291,6 +326,9 @@ const header = tv({ }, }) +/** + * @deprecated + */ const SidebarHeader = ({ className, ref, ...props }: React.ComponentProps<'div'>) => { const { state } = use(SidebarContext)! return ( @@ -302,7 +340,9 @@ const SidebarHeader = ({ className, ref, ...props }: React.ComponentProps<'div'> /> ) } - +/** + * @deprecated + */ const footer = tv({ base: [ 'mt-auto flex flex-col p-2', @@ -331,7 +371,9 @@ const footer = tv({ }, }, }) - +/** + * @deprecated + */ const SidebarFooter = ({ className, ...props }: React.ComponentProps<'div'>) => { const { state, isMobile } = useSidebar() const collapsed = state === 'collapsed' && !isMobile @@ -348,7 +390,9 @@ const SidebarFooter = ({ className, ...props }: React.ComponentProps<'div'>) => /> ) } - +/** + * @deprecated + */ const SidebarContent = ({ className, ...props }: React.ComponentProps<'div'>) => { const { state } = useSidebar() return ( @@ -363,7 +407,9 @@ const SidebarContent = ({ className, ...props }: React.ComponentProps<'div'>) => /> ) } - +/** + * @deprecated + */ const SidebarSectionGroup = ({ className, ...props }: React.ComponentProps<'section'>) => { const { state, isMobile } = useSidebar() const collapsed = state === 'collapsed' && !isMobile @@ -380,9 +426,15 @@ const SidebarSectionGroup = ({ className, ...props }: React.ComponentProps<'sect ) } +/** + * @deprecated + */ interface SidebarSectionProps extends React.ComponentProps<'div'> { label?: string } +/** + * @deprecated + */ const SidebarSection = ({ className, ...props }: SidebarSectionProps) => { const { state } = useSidebar() return ( @@ -405,6 +457,9 @@ const SidebarSection = ({ className, ...props }: SidebarSectionProps) => { ) } +/** + * @deprecated + */ const sidebarItemStyles = tv({ base: [ 'group/sidebar-item relative col-span-full cursor-pointer overflow-hidden text-sidebar-fg/70 focus-visible:outline-hidden sm:text-sm', @@ -440,6 +495,9 @@ const sidebarItemStyles = tv({ ], }) +/** + * @deprecated + */ interface SidebarItemProps extends Omit, 'children'> { ghost?: boolean isCurrent?: boolean @@ -452,6 +510,9 @@ interface SidebarItemProps extends Omit, 'chil badge?: string | number | undefined } +/** + * @deprecated + */ const SidebarItem = ({ isCurrent, tooltip, @@ -523,6 +584,9 @@ const SidebarItem = ({ ) } +/** + * @deprecated + */ const sidebarLink = tv({ base: 'col-span-full items-center focus:outline-hidden', variants: { @@ -534,8 +598,14 @@ const sidebarLink = tv({ }, }) +/** + * @deprecated + */ interface SidebarLinkProps extends LinkProps {} +/** + * @deprecated + */ const SidebarLink = React.forwardRef( ({ className, ...props }, ref) => { const { state, isMobile } = useSidebar() @@ -556,6 +626,9 @@ const SidebarLink = React.forwardRef( } ) +/** + * @deprecated + */ const SidebarInset = ({ className, ref, ...props }: React.ComponentProps<'main'>) => { return (
) } +/** + * @deprecated + */ interface SidebarDisclosureGroupProps extends DisclosureGroupProps {} + +/** + * @deprecated + */ const SidebarDisclosureGroup = ({ allowsMultipleExpanded = true, className, @@ -587,8 +667,14 @@ const SidebarDisclosureGroup = ({ ) } +/** + * @deprecated + */ interface SidebarDisclosureProps extends DisclosureProps {} +/** + * @deprecated + */ const SidebarDisclosure = React.forwardRef>( ({ className, ...props }, ref) => { const { state } = useSidebar() @@ -606,6 +692,9 @@ const SidebarDisclosure = React.forwardRef( ({ className, onClick, ...props }, ref) => { const { state, isMobile, toggleSidebar } = useSidebar() @@ -670,6 +765,9 @@ const SidebarDisclosureTrigger = React.forwardRef) => { return ( { return ( { ) } +/** + * @deprecated + */ const SidebarTrigger = ({ onPress, children, @@ -724,6 +828,9 @@ const SidebarTrigger = ({ ) } +/** + * @deprecated + */ const SidebarRail = ({ className, ref, ...props }: React.ComponentProps<'button'>) => { const { toggleSidebar } = useSidebar() @@ -748,8 +855,14 @@ const SidebarRail = ({ className, ref, ...props }: React.ComponentProps<'button' ) } +/** + * @deprecated + */ type SidebarLabelProps = React.ComponentProps +/** + * @deprecated + */ const SidebarLabel = ({ className, ref, ...props }: SidebarLabelProps) => { const { state, isMobile } = useSidebar() const collapsed = state === 'collapsed' && !isMobile @@ -772,6 +885,9 @@ const SidebarLabel = ({ className, ref, ...props }: SidebarLabelProps) => { return null } +/** + * @deprecated + */ const nav = tv({ base: [ 'isolate flex h-[3.2rem] items-center justify-between gap-x-2 px-4 text-navbar-fg sm:justify-start md:w-full', @@ -784,10 +900,16 @@ const nav = tv({ }, }) +/** + * @deprecated + */ interface SidebarNavProps extends React.ComponentProps<'nav'> { isSticky?: boolean } +/** + * @deprecated + */ const SidebarNav = ({ isSticky = false, className, ...props }: SidebarNavProps) => { return
+ +
+ This is the collapsible content that can be shown or hidden. +
+
+ +
+ ) +} + +// File Tree Style +function FileTreeStyle() { + const [isDocumentsOpen, setIsDocumentsOpen] = React.useState(true) + const [isImagesOpen, setIsImagesOpen] = React.useState(false) + const [isVideosOpen, setIsVideosOpen] = React.useState(false) + + return ( +
+
+ + + + + +
+ + report.pdf +
+
+ + presentation.pptx +
+
+ + notes.txt +
+
+
+ + + + + + +
+ + photo1.jpg +
+
+ + photo2.png +
+
+ + screenshot.png +
+
+
+ + + + + + +
+ + tutorial.mp4 +
+
+ + demo.mov +
+
+ + audio.mp3 +
+
+
+
+
+ ) +} + +// FAQ Style +function FAQStyle() { + const [openItems, setOpenItems] = React.useState([]) + + const toggleItem = (itemId: string) => { + setOpenItems((prev) => + prev.includes(itemId) ? prev.filter((id) => id !== itemId) : [...prev, itemId] + ) + } + + const faqItems = [ + { + id: '1', + question: 'What is this component library?', + answer: + 'This is a comprehensive React component library built with modern design principles and accessibility in mind.', + }, + { + id: '2', + question: 'How do I install the components?', + answer: + 'You can install the components using npm or yarn. Check the documentation for detailed installation instructions.', + }, + { + id: '3', + question: 'Is it free to use?', + answer: + 'Yes, this component library is open source and free to use in both personal and commercial projects.', + }, + ] + + return ( +
+
+ {faqItems.map((item) => ( + toggleItem(item.id)} + > + + + + +
{item.answer}
+
+
+ ))} +
+
+ ) +} + +// Settings Panel Style +function SettingsPanelStyle() { + const [openSections, setOpenSections] = React.useState(['general']) + + const toggleSection = (sectionId: string) => { + setOpenSections((prev) => + prev.includes(sectionId) ? prev.filter((id) => id !== sectionId) : [...prev, sectionId] + ) + } + + const settingsSections = [ + { + id: 'general', + title: 'General Settings', + content: ( +
+
+ Dark Mode + +
+
+ Notifications + +
+
+ Language + +
+
+ ), + }, + { + id: 'privacy', + title: 'Privacy & Security', + content: ( +
+
+ Two-Factor Authentication + +
+
+ Data Export + +
+
+ Account Deletion + +
+
+ ), + }, + { + id: 'advanced', + title: 'Advanced Options', + content: ( +
+
+ Debug Mode + +
+
+ Cache Management + +
+
+ Reset Settings + +
+
+ ), + }, + ] + + return ( +
+
+ {settingsSections.map((section) => ( +
+ toggleSection(section.id)} + > + + + + {section.content} + +
+ ))} +
+
+ ) +} + +// Multiple Independent Collapsibles +function MultipleIndependentCollapsibles() { + const [openStates, setOpenStates] = React.useState>({ + section1: false, + section2: true, + section3: false, + }) + + const toggleSection = (sectionId: string) => { + setOpenStates((prev) => ({ + ...prev, + [sectionId]: !prev[sectionId], + })) + } + + return ( +
+
+ toggleSection('section1')}> + + + + +
+

+ This is the content for section 1. Each collapsible can be controlled independently. +

+
+
+
+ + toggleSection('section2')}> + + + + +
+

+ This section starts open by default. You can see how the state is managed + independently. +

+
+
+
+ + toggleSection('section3')}> + + + + +
+

+ This is the third section. Notice how each collapsible maintains its own state. +

+
+
+
+
+
+ ) +} + +export function CollapsibleSection() { + return ( +
+ + + A simple collapsible component that can show or hide content. + +
+ +
+
+ + + + Collapsible components styled like a file tree with folders and files. + +
+ +
+
+ + + + Frequently asked questions styled as collapsible sections. + +
+ +
+
+ + + + A settings panel with collapsible sections for different categories. + +
+ +
+
+ + + + Multiple collapsible sections that can be controlled independently. + +
+ +
+
+
+ ) +} 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 b7015d98..671ac738 100644 --- a/examples/ui-playground/src/app/playground/shadcn/combobox-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/combobox-section.tsx @@ -213,7 +213,7 @@ function CustomTriggerComboboxMultiple() { {(selectedItems) => ( + + + + + Profile + + + + Settings + + + + Favorites + + + + + Delete + + + +
+ ) +} + +// Dropdown with Icons and Shortcuts +function DropdownWithIconsAndShortcuts() { + return ( +
+ + + + + + + + Edit + ⌘E + + + + Copy + ⌘C + + + + Share + ⌘S + + + + + Download + ⌘D + + + + Delete + ⌘⌫ + + + +
+ ) +} + +// Dropdown with Groups +function DropdownWithGroups() { + return ( +
+ + + + + + File Operations + + + + New File + + + + Open Folder + + + + Save As + + + + Export + + + + Export as PDF + + + + Export as Image + + + + +
+ ) +} + +// Dropdown with Checkboxes +function DropdownWithCheckboxes() { + const [showStatusBar, setShowStatusBar] = React.useState(true) + const [showPanel, setShowPanel] = React.useState(false) + const [showSidebar, setShowSidebar] = React.useState(true) + + return ( +
+ + + + + + View + + Status Bar + + + Panel + + + Sidebar + + + +
+ ) +} + +// Dropdown with Radio Items +function DropdownWithRadioItems() { + const [theme, setTheme] = React.useState('light') + + return ( +
+ + + + + + Choose Theme + + Light + Dark + System + + + +
+ ) +} + +// Dropdown with Submenus +function DropdownWithSubmenus() { + return ( +
+ + + + + + + + Edit + + + + Copy + + + + + + Share + + + Email + Twitter + LinkedIn + + Copy Link + + + + + + Delete + + + +
+ ) +} + +// Context Menu Style +function ContextMenuStyle() { + return ( +
+ + + + + + + + Edit + + + + Duplicate + + + + + Share + + + + Download + + + + + Delete + + + +
+ ) +} + +export function DropdownMenuSection() { + return ( +
+ + + A simple dropdown menu with basic menu items and separators. + +
+ +
+
+ + + + Dropdown menus can include icons and keyboard shortcuts for better UX. + +
+ +
+
+ + + + Organize menu items into logical groups with labels and separators. + +
+ +
+
+ + + + Interactive checkboxes for toggleable options within dropdown menus. + +
+ +
+
+ + + + Radio button groups for selecting one option from multiple choices. + +
+ +
+
+ + + + Nested dropdown menus for organizing complex menu structures. + +
+ +
+
+ + + + A compact dropdown menu suitable for context menus and action buttons. + +
+ +
+
+
+ ) +} diff --git a/examples/ui-playground/src/app/playground/shadcn/page.tsx b/examples/ui-playground/src/app/playground/shadcn/page.tsx index e3407e49..7e72b72a 100644 --- a/examples/ui-playground/src/app/playground/shadcn/page.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/page.tsx @@ -1,3 +1,4 @@ +'use client' import * as React from 'react' import Link from 'next/link' @@ -6,9 +7,12 @@ import { Typography } from '@genseki/react' import { linkVariants } from '@genseki/react/v2' import { ButtonSection } from './button-section' +import { CollapsibleSection } from './collapsible-section' import { ComboboxSection } from './combobox-section' +import { DropdownMenuSection } from './dropdown-menu-section' import { InputSection } from './input-section' import { LinkSection } from './link-section' +import { TooltipSection } from './tooltip-section' export default function ComboboxPage() { return ( @@ -37,6 +41,21 @@ export default function ComboboxPage() { Link + + + Tooltip + + + + + Dropdown Menu + + + + + Collapsible + +
@@ -72,6 +91,27 @@ export default function ComboboxPage() { {'>'} Link
+ + {'>'} Tooltip + +
+ + {'>'} Dropdown Menu + +
+ + {'>'} Collapsible + +
) { return ( - + -
- Footer content -
+
diff --git a/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-footer.tsx b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-footer.tsx new file mode 100644 index 00000000..5b7c33ec --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-footer.tsx @@ -0,0 +1,58 @@ +import { CaretUpDownIcon, SignOutIcon, UserIcon } from '@phosphor-icons/react' + +import { Typography } from '@genseki/react' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + SidebarMenu, + SidebarMenuItem, +} from '@genseki/react/v2' + +import { cn } from '../../../../../../../../packages/react/src/react/utils/cn' + +function NavFooter() { + return ( + + + + + + + + + + Signout + + + + + + ) +} + +export { NavFooter } diff --git a/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-header.tsx b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-header.tsx index a59d1890..16bc966b 100644 --- a/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-header.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-header.tsx @@ -1,32 +1,34 @@ +import { MoonStarsIcon } from '@phosphor-icons/react' + import { Typography } from '@genseki/react' -import { SidebarMenu, SidebarMenuItem, useSidebar } from '@genseki/react/v2' +import { SidebarMenu, SidebarMenuItem, SidebarTrigger } from '@genseki/react/v2' function NavHeader() { - const ctx = useSidebar() - return ( - - {/*
-
-
-
- -
-
- - {props.title} - - - {props.version} - + +
+
+
+
+
+ +
+ +
+ + BRV + + + 1.0.1 + +
-
*/} -
- - BRV - +
diff --git a/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-main.tsx b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-main.tsx index 69ab9047..72cf2058 100644 --- a/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-main.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/sidebar/components/nav-main.tsx @@ -2,7 +2,9 @@ import type { Icon } from '@phosphor-icons/react' import { CaretRightIcon } from '@phosphor-icons/react' +import Link from 'next/link' +import { Typography } from '@genseki/react' import { Collapsible, CollapsibleContent, @@ -16,6 +18,8 @@ import { SidebarMenuSubItem, } from '@genseki/react/v2' +import { cn } from '../../../../../../../../packages/react/src/react/utils/cn' + function NavMain({ items, }: { @@ -31,7 +35,7 @@ function NavMain({ }[] }) { return ( - + {items.map((item) => ( - + - - {item.icon && } - {item.title} - + + {item.icon && ( + + )} + + {item.title} + + - + {item.items?.map((subItem) => ( - - - {subItem.title} - + + + + {subItem.title} + + ))} diff --git a/examples/ui-playground/src/app/playground/shadcn/sidebar/page.tsx b/examples/ui-playground/src/app/playground/shadcn/sidebar/page.tsx index 683fa631..23faafa7 100644 --- a/examples/ui-playground/src/app/playground/shadcn/sidebar/page.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/sidebar/page.tsx @@ -2,12 +2,12 @@ import { Breadcrumb, + BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, - Separator, SidebarInset, SidebarProvider, SidebarTrigger, @@ -20,16 +20,19 @@ function DummySidebarPage() { -
+
- - + + + Building Your Application + + Data Fetching diff --git a/examples/ui-playground/src/app/playground/shadcn/tooltip-section.tsx b/examples/ui-playground/src/app/playground/shadcn/tooltip-section.tsx new file mode 100644 index 00000000..fed62785 --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/tooltip-section.tsx @@ -0,0 +1,277 @@ +import React from 'react' + +import { + InfoIcon, + MagnifyingGlassIcon, + PaperPlaneRightIcon, + QuestionMarkIcon, + WarningIcon, +} from '@phosphor-icons/react' + +import { Typography } from '@genseki/react' +import { Button } from '@genseki/react/v2' +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@genseki/react/v2' + +import { PlaygroundCard } from '~/src/components/card' + +// Basic Tooltip +function BasicTooltip() { + return ( +
+ + + + + +

This is a basic tooltip

+
+
+
+ ) +} + +// Tooltip with Icon +function TooltipWithIcon() { + return ( +
+ + + + + +

Information tooltip

+
+
+ + + + + + +

Warning tooltip

+
+
+ + + + + + +

Help tooltip

+
+
+
+ ) +} + +// Tooltip Positions +function TooltipPositions() { + return ( +
+
+ + + + + +

Tooltip on top

+
+
+
+ +
+ + + + + +

Tooltip on bottom

+
+
+
+ +
+ + + + + +

Tooltip on left

+
+
+
+ +
+ + + + + +

Tooltip on right

+
+
+
+
+ ) +} + +// Tooltip with Long Content +function TooltipLongContent() { + return ( +
+ + + + + + Send this email to the recipient. This action cannot be undone. + + + + + + + + +

Search through all your documents and files for specific content.

+
+
+
+ ) +} + +// Tooltip Alignment +function TooltipAlignment() { + return ( +
+
+ + + + + +

Aligned to start

+
+
+
+ +
+ + + + + +

Aligned to center

+
+
+
+ +
+ + + + + +

Aligned to end

+
+
+
+
+ ) +} + +// Disabled Tooltip +function DisabledTooltip() { + return ( +
+ + + + + +

This button is disabled

+
+
+ + +
+ ) +} + +export function TooltipSection() { + return ( + +
+ + + A simple tooltip that appears on hover. + +
+ +
+
+ + + + Tooltips can be used with icon buttons to provide additional context. + +
+ +
+
+ + + + Tooltips can be positioned on any side of the trigger element. + +
+ +
+
+ + + + Tooltips can contain longer text content with proper wrapping. + +
+ +
+
+ + + + Control the alignment of tooltips relative to their trigger element. + +
+ +
+
+ + + + Tooltips work with disabled elements to provide helpful information. + +
+ +
+
+
+
+ ) +} diff --git a/packages/react/package.json b/packages/react/package.json index 03697421..ffdaa89e 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -54,6 +54,7 @@ "@phosphor-icons/react": "^2.1.8", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-separator": "^1.1.7", diff --git a/packages/react/src/react/components/primitives/button.tsx b/packages/react/src/react/components/primitives/button.tsx index 8d83521f..a5ff9aac 100644 --- a/packages/react/src/react/components/primitives/button.tsx +++ b/packages/react/src/react/components/primitives/button.tsx @@ -148,7 +148,7 @@ interface ButtonLinkProps /** * @deprecated */ -const AriaButtonLink = React.forwardRef(function ButtonLink( +const ButtonLink = React.forwardRef(function ButtonLink( { className, variant, size, isDisabled = false, isPending = false, children, ...props }, ref ) { @@ -184,4 +184,4 @@ const AriaButtonLink = React.forwardRef(func ) }) -export { AriaButtonLink, Button, type ButtonLinkProps, type ButtonProps } +export { Button, ButtonLink, type ButtonLinkProps, type ButtonProps } diff --git a/packages/react/v2/components/primitives/breadcrumb.tsx b/packages/react/v2/components/primitives/breadcrumb.tsx index 57db7724..aaf4f066 100644 --- a/packages/react/v2/components/primitives/breadcrumb.tsx +++ b/packages/react/v2/components/primitives/breadcrumb.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import { CaretRightIcon, DotsThreeIcon } from '@phosphor-icons/react' +import { DotsThreeIcon } from '@phosphor-icons/react' import { Slot } from '@radix-ui/react-slot' import { cn } from '../../../src/react/utils/cn' @@ -14,7 +14,7 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
    svg]:size-7', className)} {...props} > - {children ?? } + {children ?? '/'} ) } diff --git a/packages/react/v2/components/primitives/dropdown-menu.tsx b/packages/react/v2/components/primitives/dropdown-menu.tsx new file mode 100644 index 00000000..8567a59e --- /dev/null +++ b/packages/react/v2/components/primitives/dropdown-menu.tsx @@ -0,0 +1,229 @@ +'use client' + +import * as React from 'react' + +import { CaretRightIcon, CheckCircleIcon, CheckIcon } from '@phosphor-icons/react' +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' + +import { cn } from '../../../src/react/utils/cn' + +function DropdownMenu({ ...props }: React.ComponentProps) { + return +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function DropdownMenuGroup({ ...props }: React.ComponentProps) { + return +} + +function DropdownMenuItem({ + className, + inset, + variant = 'default', + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: 'default' | 'destructive' +}) { + return ( + + ) +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) { + return ( + + ) +} + +function DropdownMenuSub({ ...props }: React.ComponentProps) { + return +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} diff --git a/packages/react/v2/components/primitives/index.ts b/packages/react/v2/components/primitives/index.ts index 86e2586b..cccacad5 100644 --- a/packages/react/v2/components/primitives/index.ts +++ b/packages/react/v2/components/primitives/index.ts @@ -5,6 +5,7 @@ export * from './collapsible' export * from './combobox' export * from './command' export * from './dialog' +export * from './dropdown-menu' export * from './input' export * from './input-group' export * from './label' diff --git a/packages/react/v2/components/primitives/sidebar.tsx b/packages/react/v2/components/primitives/sidebar.tsx index 0d39b6bd..385f7a40 100644 --- a/packages/react/v2/components/primitives/sidebar.tsx +++ b/packages/react/v2/components/primitives/sidebar.tsx @@ -19,9 +19,9 @@ import { cn } from '../../../src/react/utils/cn' const SIDEBAR_COOKIE_NAME = 'sidebar-state' const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 -const SIDEBAR_WIDTH = '16rem' +const SIDEBAR_WIDTH = '17rem' const SIDEBAR_WIDTH_MOBILE = '18rem' -const SIDEBAR_WIDTH_ICON = '3rem' +const SIDEBAR_WIDTH_ICON = '3.75rem' const SIDEBAR_KEYBOARD_SHORTCUT = 'b' type SidebarContextProps = { @@ -382,7 +382,7 @@ function SidebarGroupLabel({ data-slot="sidebar-group-label" data-sidebar="group-label" className={cn( - 'text-sidebar-foreground/70 ring-sidebar-ring flex h-16 shrink-0 items-center rounded-md px-4 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-4 [&>svg]:size-8 [&>svg]:shrink-0', + 'text-sidebar-foreground/70 ring-sidebar-ring flex h-16 shrink-0 items-center rounded-md px-4 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-8 [&>svg]:shrink-0', 'group-data-[collapsible=icon]:-mt-16 group-data-[collapsible=icon]:opacity-0', className )} @@ -403,7 +403,7 @@ function SidebarGroupAction({ data-slot="sidebar-group-action" data-sidebar="group-action" className={cn( - 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-7 right-6 flex aspect-square w-10 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-4 [&>svg]:size-8 [&>svg]:shrink-0', + 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-7 right-6 flex aspect-square w-10 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-8 [&>svg]:shrink-0', // Increases the hit area of the button on mobile. 'after:absolute after:-inset-4 md:after:hidden', 'group-data-[collapsible=icon]:hidden', @@ -448,7 +448,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) { } const sidebarMenuButtonVariants = cva( - 'peer/menu-button flex w-full items-center gap-4 overflow-hidden rounded-md p-4 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-4 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-16 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-16! group-data-[collapsible=icon]:p-4! [&>span:last-child]:truncate [&>svg]:size-8 [&>svg]:shrink-0', + 'peer/menu-button flex w-full items-center gap-4 overflow-hidden rounded-md p-4 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-16 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-16! group-data-[collapsible=icon]:p-4! [&>span:last-child]:truncate [&>svg]:size-8 [&>svg]:shrink-0', { variants: { variant: { @@ -535,7 +535,7 @@ function SidebarMenuAction({ data-slot="sidebar-menu-action" data-sidebar="menu-action" className={cn( - 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-3 right-2 flex aspect-square w-10 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-4 [&>svg]:size-8 [&>svg]:shrink-0', + 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-3 right-2 flex aspect-square w-10 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-8 [&>svg]:shrink-0', // Increases the hit area of the button on mobile. 'after:absolute after:-inset-4 md:after:hidden', 'peer-data-[size=sm]/menu-button:top-2', @@ -649,7 +649,7 @@ function SidebarMenuSubButton({ data-size={size} data-active={isActive} className={cn( - 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-14 min-w-0 -translate-x-px items-center gap-4 overflow-hidden rounded-md px-4 outline-hidden focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-8 [&>svg]:shrink-0', + 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-14 min-w-0 -translate-x-px items-center gap-4 overflow-hidden rounded-md px-4 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-8 [&>svg]:shrink-0', 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground', size === 'sm' && 'text-xs', size === 'md' && 'text-sm', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59dbeee3..f83376ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -430,6 +430,9 @@ importers: '@radix-ui/react-dialog': specifier: ^1.1.15 version: 1.1.15(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-label': specifier: ^2.1.7 version: 2.1.7(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -1543,6 +1546,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + 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-compose-refs@1.1.2': resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: @@ -1574,6 +1590,15 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + 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.11': resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} peerDependencies: @@ -1587,6 +1612,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + 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-focus-guards@1.1.3': resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} peerDependencies: @@ -1631,6 +1669,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + 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-popover@1.1.15': resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} peerDependencies: @@ -1696,6 +1747,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + 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.7': resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} peerDependencies: @@ -6843,6 +6907,18 @@ snapshots: '@types/react': 19.1.6 '@types/react-dom': 19.1.6(@types/react@19.1.6) + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.6)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.6 + '@types/react-dom': 19.1.6(@types/react@19.1.6) + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.6)(react@19.1.0)': dependencies: react: 19.1.0 @@ -6877,6 +6953,12 @@ snapshots: '@types/react': 19.1.6 '@types/react-dom': 19.1.6(@types/react@19.1.6) + '@radix-ui/react-direction@1.1.1(@types/react@19.1.6)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.6 + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -6890,6 +6972,21 @@ snapshots: '@types/react': 19.1.6 '@types/react-dom': 19.1.6(@types/react@19.1.6) + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.6)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.6 + '@types/react-dom': 19.1.6(@types/react@19.1.6) + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.6)(react@19.1.0)': dependencies: react: 19.1.0 @@ -6923,6 +7020,32 @@ snapshots: '@types/react': 19.1.6 '@types/react-dom': 19.1.6(@types/react@19.1.6) + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.6)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.6)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.6 + '@types/react-dom': 19.1.6(@types/react@19.1.6) + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -6993,6 +7116,23 @@ snapshots: '@types/react': 19.1.6 '@types/react-dom': 19.1.6(@types/react@19.1.6) + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.6)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.6)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.6 + '@types/react-dom': 19.1.6(@types/react@19.1.6) + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) From f468c0278c7ff330d80ae2ee079e93324c67fd4d Mon Sep 17 00:00:00 2001 From: supakornnetsuwan Date: Fri, 17 Oct 2025 15:28:39 +0700 Subject: [PATCH 3/4] chore: add changeset --- .changeset/loose-cycles-create.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changeset/loose-cycles-create.md diff --git a/.changeset/loose-cycles-create.md b/.changeset/loose-cycles-create.md new file mode 100644 index 00000000..30d9c6db --- /dev/null +++ b/.changeset/loose-cycles-create.md @@ -0,0 +1,12 @@ +--- +'@example/ui-playground': patch +'@genseki/react': patch +--- + +Migrate UI to shadcn + +- New `sidebar` component +- New `breadcrumb` component +- New `dropdown-menu` component +- New `skeleton` component +- New `tooltip` component From b56b935cb293e845a043336dfae634d9a4a70314 Mon Sep 17 00:00:00 2001 From: supakornnetsuwan Date: Fri, 17 Oct 2025 16:00:25 +0700 Subject: [PATCH 4/4] fix: prevent dropdown menu close after selection example, change button focus ring color --- .../shadcn/dropdown-menu-section.tsx | 24 ++++++++++++++++--- .../react/v2/components/primitives/button.tsx | 4 ++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/examples/ui-playground/src/app/playground/shadcn/dropdown-menu-section.tsx b/examples/ui-playground/src/app/playground/shadcn/dropdown-menu-section.tsx index a1f77fa4..1825fd84 100644 --- a/examples/ui-playground/src/app/playground/shadcn/dropdown-menu-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/dropdown-menu-section.tsx @@ -166,13 +166,31 @@ function DropdownWithCheckboxes() { View - + { + e.preventDefault() + }} + checked={showStatusBar} + onCheckedChange={setShowStatusBar} + > Status Bar - + { + e.preventDefault() + }} + checked={showPanel} + onCheckedChange={setShowPanel} + > Panel - + { + e.preventDefault() + }} + checked={showSidebar} + onCheckedChange={setShowSidebar} + > Sidebar diff --git a/packages/react/v2/components/primitives/button.tsx b/packages/react/v2/components/primitives/button.tsx index 1cc94075..e3a34ae7 100644 --- a/packages/react/v2/components/primitives/button.tsx +++ b/packages/react/v2/components/primitives/button.tsx @@ -11,7 +11,7 @@ import { cn } from '../../../src/react/utils/cn' const buttonVariants = cva( [ - "inline-flex items-center justify-center gap-4 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-8 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring ring-offset-1 focus-visible:ring-ring/50 focus-visible:ring-[2px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-4 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-8 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring ring-offset-1 focus-visible:ring-ring focus-visible:ring-[2px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 'disabled:opacity-80 cursor-pointer', ], { @@ -30,7 +30,7 @@ const buttonVariants = cva( ghost: ' bg-surface-button-ghost hover:bg-surface-button-ghost-hover active:bg-surface-button-ghost text-text-secondary hover:text-accent-foreground dark:hover:bg-accent/50', destructive: - 'bg-destructive text-white hover:bg-destructive/90 active:bg-destructive focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + 'bg-destructive text-white hover:bg-destructive/90 active:bg-destructive focus-visible:ring-destructive dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', }, size: { default: 'h-18 px-8 py-4 has-[>svg]:px-6',