From 8b32103d450f19c6ee87e70197909fc937a25d3d Mon Sep 17 00:00:00 2001 From: supakornnetsuwan Date: Tue, 21 Oct 2025 12:12:13 +0700 Subject: [PATCH 1/3] migrate `date-picker`, `time-input` and more fixes 1. `typography` component now in the `v2` 2. Add example for server side sidebar toggle state as a default value (Via data hydration) 3. Fix `breadcrumb` focus ring color 4. Fix `sidebar-footer` example, focus ring color. 5. Fix `group-input-addon` focusing state 6. Fix `group-input` disable background color 7. Change button variant `default` to `md` 8. Make `button-section` and `input-section` default background color to secondary color, so background color of input will be noticable 9. Add `date-picker` examples (at `date-picker-section`) 10. Add `time-input` examples (at `input-section`) 11. New dependencies `date-fns` for `@example/ui-playground` and `react-day-picker` as a peer dependency for `@genseki/react` --- examples/erp/genseki/auth/setup/setup.tsx | 2 +- examples/ui-playground/package.json | 2 + .../app/playground/shadcn/button-section.tsx | 33 +- .../playground/shadcn/collapsible-section.tsx | 3 +- .../playground/shadcn/combobox-section.tsx | 2 +- .../playground/shadcn/date-picker-section.tsx | 639 ++++++++++++++++++ .../shadcn/dropdown-menu-section.tsx | 2 +- .../app/playground/shadcn/input-section.tsx | 65 +- .../app/playground/shadcn/link-section.tsx | 3 +- .../src/app/playground/shadcn/page.tsx | 16 +- .../shadcn/sidebar/components/app-sidebar.tsx | 1 + .../shadcn/sidebar/components/nav-footer.tsx | 4 +- .../shadcn/sidebar/components/nav-header.tsx | 3 +- .../shadcn/sidebar/components/nav-main.tsx | 2 +- .../app/playground/shadcn/sidebar/page.tsx | 30 +- .../playground/shadcn/textarea-section.tsx | 3 +- .../app/playground/shadcn/toast-section.tsx | 3 +- .../shadcn/toggle-group-section.tsx | 16 +- .../app/playground/shadcn/toggle-section.tsx | 42 +- .../app/playground/shadcn/tooltip-section.tsx | 3 +- .../ui-playground/src/components/card.tsx | 2 +- .../ui-playground/src/components/wrapper.tsx | 2 +- packages/react/package.json | 3 + .../views/forgot-password/forgot-password.tsx | 2 +- .../views/reset-password/reset-password.tsx | 2 +- .../react/components/primitives/calendar.tsx | 18 + .../components/primitives/typography.tsx | 15 + .../v2/components/primitives/breadcrumb.tsx | 7 +- .../react/v2/components/primitives/button.tsx | 16 +- .../v2/components/primitives/calendar.tsx | 229 +++++++ .../v2/components/primitives/date-picker.tsx | 81 +++ .../react/v2/components/primitives/index.ts | 3 + .../v2/components/primitives/input-group.tsx | 130 ++-- .../v2/components/primitives/toggle-group.tsx | 2 +- .../react/v2/components/primitives/toggle.tsx | 4 +- .../v2/components/primitives/typography.tsx | 57 ++ pnpm-lock.yaml | 46 +- 37 files changed, 1323 insertions(+), 170 deletions(-) create mode 100644 examples/ui-playground/src/app/playground/shadcn/date-picker-section.tsx create mode 100644 packages/react/v2/components/primitives/calendar.tsx create mode 100644 packages/react/v2/components/primitives/date-picker.tsx create mode 100644 packages/react/v2/components/primitives/typography.tsx diff --git a/examples/erp/genseki/auth/setup/setup.tsx b/examples/erp/genseki/auth/setup/setup.tsx index 77211c31..c66783ef 100644 --- a/examples/erp/genseki/auth/setup/setup.tsx +++ b/examples/erp/genseki/auth/setup/setup.tsx @@ -1,4 +1,4 @@ -import { Typography } from '@genseki/react' +import { Typography } from '@genseki/react/v2' import { SetupClientForm } from './setup.client' diff --git a/examples/ui-playground/package.json b/examples/ui-playground/package.json index 22a5c665..8eb76595 100644 --- a/examples/ui-playground/package.json +++ b/examples/ui-playground/package.json @@ -21,10 +21,12 @@ "@tailwindcss/postcss": "^4.1.7", "@tanstack/react-query": "^5.71.5", "@tiptap/starter-kit": "^2.26.3", + "date-fns": "^4.1.0", "next": "15.2.2", "next-themes": "^0.4.6", "postcss": "^8.5.3", "react": "19.1.0", + "react-day-picker": "^9.11.1", "react-dom": "19.1.0", "tailwindcss": "^4.1.7", "zod": "4.0.11" diff --git a/examples/ui-playground/src/app/playground/shadcn/button-section.tsx b/examples/ui-playground/src/app/playground/shadcn/button-section.tsx index b090fc1b..2410f162 100644 --- a/examples/ui-playground/src/app/playground/shadcn/button-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/button-section.tsx @@ -15,8 +15,13 @@ import { SortDescendingIcon, } from '@phosphor-icons/react' -import { Typography } from '@genseki/react' -import { Button, ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from '@genseki/react/v2' +import { + Button, + ButtonGroup, + ButtonGroupSeparator, + ButtonGroupText, + Typography, +} from '@genseki/react/v2' import { PlaygroundCard } from '~/src/components/card' @@ -68,7 +73,7 @@ function ButtonSizes() { Small - @@ -270,7 +275,7 @@ export function ButtonSection() { Different button variants with various styles and purposes. -
+
@@ -279,7 +284,7 @@ export function ButtonSection() { Buttons come in three sizes: small, default, and large. -
+
@@ -288,7 +293,7 @@ export function ButtonSection() { Icon-only buttons in different sizes. -
+
@@ -297,7 +302,7 @@ export function ButtonSection() { Buttons with icons alongside text labels. -
+
@@ -306,7 +311,7 @@ export function ButtonSection() { Buttons can be disabled or show a pending/loading state. -
+
@@ -315,7 +320,7 @@ export function ButtonSection() { A button that shows loading state during async operations. -
+
@@ -324,7 +329,7 @@ export function ButtonSection() { A simple button group with connected buttons. -
+
@@ -333,7 +338,7 @@ export function ButtonSection() { Button groups with separators between buttons for visual distinction. -
+
@@ -342,7 +347,7 @@ export function ButtonSection() { Button groups with text labels and icons for better context. -
+
@@ -351,7 +356,7 @@ export function ButtonSection() { Vertical button groups for stacked layouts. -
+
@@ -360,7 +365,7 @@ export function ButtonSection() { Button groups with different button variants mixed together. -
+
diff --git a/examples/ui-playground/src/app/playground/shadcn/collapsible-section.tsx b/examples/ui-playground/src/app/playground/shadcn/collapsible-section.tsx index 7ec2570d..981e000e 100644 --- a/examples/ui-playground/src/app/playground/shadcn/collapsible-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/collapsible-section.tsx @@ -11,8 +11,7 @@ import { VideoIcon, } from '@phosphor-icons/react' -import { Typography } from '@genseki/react' -import { Button } from '@genseki/react/v2' +import { Button, Typography } from '@genseki/react/v2' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@genseki/react/v2' import { PlaygroundCard } from '~/src/components/card' 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 671ac738..f4f7f615 100644 --- a/examples/ui-playground/src/app/playground/shadcn/combobox-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/combobox-section.tsx @@ -2,7 +2,6 @@ import React from 'react' import { CheckIcon } from '@phosphor-icons/react' -import { Typography } from '@genseki/react' import { Button, ComboboxCommandEmpty, @@ -14,6 +13,7 @@ import { ComboboxProvider, ComboboxTrigger, ComboboxTriggerMultiValue, + Typography, } from '@genseki/react/v2' import { PlaygroundCard } from '~/src/components/card' diff --git a/examples/ui-playground/src/app/playground/shadcn/date-picker-section.tsx b/examples/ui-playground/src/app/playground/shadcn/date-picker-section.tsx new file mode 100644 index 00000000..d3f50adf --- /dev/null +++ b/examples/ui-playground/src/app/playground/shadcn/date-picker-section.tsx @@ -0,0 +1,639 @@ +import React, { useState } from 'react' +import type { DateRange } from 'react-day-picker' + +import { CalendarDotsIcon, ClockIcon, WarningIcon } from '@phosphor-icons/react' +import { + addDays, + endOfMonth, + endOfWeek, + format, + startOfMonth, + startOfWeek, + subDays, +} from 'date-fns' + +import { InputGroup, InputGroupControl } from '@genseki/react' +import { + Button, + Calendar, + CalendarDayButton, + DatePickerContent, + DatePickerProvider, + DatePickerTrigger, + Input, + Popover, + PopoverContent, + PopoverTrigger, + Typography, +} from '@genseki/react/v2' + +import { PlaygroundCard } from '../../../components/card' + +function BasicDatePicker() { + const [date, setDate] = useState() + + return ( +
+ + + + + + +
+ ) +} + +function CustomDatePicker() { + const [date, setDate] = useState() + const [open, setOpen] = useState(false) + + return ( +
+
+ + + + + + +
+
+ + +
+
+
+
+ + {date + ? `Booking date: ${format(date, 'dd/MM/yyyy HH:mm:ss')}` + : 'Please choose the reservation date'} + +
+ ) +} + +/** + * @description This is an example from Shadcn + */ +function FromScratchDatePicker() { + const [date, setDate] = useState(undefined) + const [open, setOpen] = useState(false) + + return ( + + + + + + + + + ) +} + +function RangeDatePicker() { + const [dateRange, setDateRange] = useState() + const [open, setOpen] = useState(false) + + const formatDateRange = (range: DateRange | undefined) => { + if (!range?.from) return 'Select date range' + if (!range.to) return format(range.from, 'dd/MM/yyyy') + return `${format(range.from, 'dd/MM/yyyy')} - ${format(range.to, 'dd/MM/yyyy')}` + } + + return ( +
+
+ + + + + + ( + + ), + }} + /> +
+
+ + +
+
+
+
+ + {dateRange?.from && dateRange?.to + ? `Selected range: ${format(dateRange.from, 'dd/MM/yyyy')} to ${format(dateRange.to, 'dd/MM/yyyy')}` + : dateRange?.from + ? `Start date: ${format(dateRange.from, 'dd/MM/yyyy')} (select end date)` + : 'Please select a date range'} + +
+ ) +} + +function DateTimePicker() { + const [date, setDate] = useState() + const [time, setTime] = useState('09:00') + const [open, setOpen] = useState(false) + + const formatDateTime = (date: Date | undefined, time: string) => { + if (!date) return 'Select date and time' + return `${format(date, 'dd/MM/yyyy')} at ${time}` + } + + const handleDateSelect = (selectedDate: Date | undefined) => { + if (selectedDate) { + const [hours, minutes] = time.split(':') + const newDateTime = new Date(selectedDate) + newDateTime.setHours(parseInt(hours), parseInt(minutes)) + setDate(newDateTime) + } else { + setDate(selectedDate) + } + } + + const handleTimeChange = (newTime: string) => { + setTime(newTime) + if (date) { + const [hours, minutes] = newTime.split(':') + const newDateTime = new Date(date) + newDateTime.setHours(parseInt(hours), parseInt(minutes)) + setDate(newDateTime) + } + } + + return ( +
+
+ + + + + + +
+
+
+ + + Time + +
+ + + handleTimeChange(e.target.value)} + /> + + + +
+ + +
+
+
+
+
+ + {date ? `Selected: ${format(date, 'dd/MM/yyyy HH:mm')}` : 'Please select a date and time'} + +
+ ) +} + +function MultiMonthDatePicker() { + const [dateRange, setDateRange] = useState() + const [open, setOpen] = useState(false) + + const formatDateRange = (range: DateRange | undefined) => { + if (!range?.from) return 'Select date range' + if (!range.to) return format(range.from, 'dd/MM/yyyy') + return `${format(range.from, 'dd/MM/yyyy')} - ${format(range.to, 'dd/MM/yyyy')}` + } + + return ( +
+
+ + + + + + +
+
+ + +
+
+
+
+ + {dateRange?.from && dateRange?.to + ? `Selected range: ${format(dateRange.from, 'dd/MM/yyyy')} to ${format(dateRange.to, 'dd/MM/yyyy')}` + : dateRange?.from + ? `Start date: ${format(dateRange.from, 'dd/MM/yyyy')} (select end date)` + : 'Please select a date range'} + +
+ ) +} + +function PresetDatePicker() { + const [dateRange, setDateRange] = useState() + const [open, setOpen] = useState(false) + + const presets = [ + { + label: 'Today', + value: { from: new Date(), to: new Date() }, + }, + { + label: 'Yesterday', + value: { from: subDays(new Date(), 1), to: subDays(new Date(), 1) }, + }, + { + label: 'Last 7 days', + value: { from: subDays(new Date(), 6), to: new Date() }, + }, + { + label: 'Last 30 days', + value: { from: subDays(new Date(), 29), to: new Date() }, + }, + { + label: 'This week', + value: { from: startOfWeek(new Date()), to: endOfWeek(new Date()) }, + }, + { + label: 'This month', + value: { from: startOfMonth(new Date()), to: endOfMonth(new Date()) }, + }, + ] + + const formatDateRange = (range: DateRange | undefined) => { + if (!range?.from) return 'Select date range' + if (!range.to) return format(range.from, 'dd/MM/yyyy') + return `${format(range.from, 'dd/MM/yyyy')} - ${format(range.to, 'dd/MM/yyyy')}` + } + + return ( +
+
+ + + + + +
+
+ + Quick Select + +
+ {presets.map((preset) => ( + + ))} +
+
+ +
+
+
+ + +
+
+
+
+ + {dateRange?.from && dateRange?.to + ? `Selected range: ${format(dateRange.from, 'dd/MM/yyyy')} to ${format(dateRange.to, 'dd/MM/yyyy')}` + : dateRange?.from + ? `Start date: ${format(dateRange.from, 'dd/MM/yyyy')} (select end date)` + : 'Please select a date range'} + +
+ ) +} + +function DisabledDatesPicker() { + return ( +
+
+ + + + + + +
+
+ ) +} + +function DisabledDates() { + const [date, setDate] = useState() + const [open, setOpen] = useState(false) + + const today = new Date() + const disabledDates = [addDays(today, 1), addDays(today, 2), addDays(today, 5), addDays(today, 8)] + + return ( +
+
+ + + + + + +
+
+ + Some dates are disabled for booking + +
+
+
+
+ + {date ? `Selected date: ${format(date, 'dd/MM/yyyy')}` : 'Please select an available date'} + +
+ ) +} + +export function DatePickerSection() { + return ( +
+
+ + Our example use{' '} + + Popover + ,{' '} + + PopoverContent + {' '} + and + + PopoverTrigger + + , but you can use{' '} + + DatePickerProvider + + , + + DatePickerContent + {' '} + and{' '} + + DatePickerTrigger + +
+ + + A simple default datepicker + +
+ +
+
+ + + + From scratch datepicker + +
+ +
+
+ + + + A custom datepicker + +
+ +
+
+ + + + A date range picker with start and end date selection + +
+ +
+
+ + + + A date picker with time selection for precise scheduling + +
+ +
+
+ + + + A range picker showing multiple months for better navigation + +
+ +
+
+ + + + A date picker with quick preset options for common date ranges + +
+ +
+
+ + + + A disabled input date picker + +
+ +
+
+ + + + A date picker with disabled dates for booking restrictions + +
+ +
+
+
+ ) +} 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 1825fd84..0aafe8b7 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 @@ -13,7 +13,6 @@ import { UserIcon, } from '@phosphor-icons/react' -import { Typography } from '@genseki/react' import { Button } from '@genseki/react/v2' import { DropdownMenu, @@ -30,6 +29,7 @@ import { DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, + Typography, } from '@genseki/react/v2' import { PlaygroundCard } from '~/src/components/card' 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 564768f5..0ed39176 100644 --- a/examples/ui-playground/src/app/playground/shadcn/input-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/input-section.tsx @@ -12,7 +12,6 @@ import { XIcon, } from '@phosphor-icons/react' -import { Typography } from '@genseki/react' import { Input, InputGroup, @@ -21,6 +20,7 @@ import { InputGroupControl, InputGroupText, Label, + Typography, } from '@genseki/react/v2' import { PlaygroundCard } from '~/src/components/card' @@ -136,6 +136,48 @@ function FileInput() { ) } +// Time Inputs +function TimeInputs() { + const [time, setTime] = React.useState('09:00') + const [startTime, setStartTime] = React.useState('09:00') + const [endTime, setEndTime] = React.useState('17:00') + + return ( +
+
+ + setTime(e.target.value)} /> + + Selected time: {time} + +
+ +
+ +
+ setStartTime(e.target.value)} + aria-label="Start time" + /> + setEndTime(e.target.value)} + aria-label="End time" + /> +
+ + {startTime} - {endTime} + +
+
+ ) +} + // Input Group - Inline Start function InputGroupInlineStart() { return ( @@ -230,12 +272,16 @@ function InputGroupWithButtons() { - + - setShowPassword(!showPassword)}> + setShowPassword(!showPassword)} + className="border-none" + > {showPassword ? : } @@ -343,14 +389,14 @@ function InputGroupStates() { - + - + @@ -425,6 +471,15 @@ export function InputSection() {
+ + + Time input and time range using native time picker UI. + +
+ +
+
+ Input groups with icons or text at the start (left side) of the input field. diff --git a/examples/ui-playground/src/app/playground/shadcn/link-section.tsx b/examples/ui-playground/src/app/playground/shadcn/link-section.tsx index 8bf58a58..81e6cf27 100644 --- a/examples/ui-playground/src/app/playground/shadcn/link-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/link-section.tsx @@ -2,8 +2,7 @@ import React from 'react' import Link from 'next/link' -import { Typography } from '@genseki/react' -import { linkVariants } from '@genseki/react/v2' +import { linkVariants, Typography } from '@genseki/react/v2' import { PlaygroundCard } from '../../../components/card' diff --git a/examples/ui-playground/src/app/playground/shadcn/page.tsx b/examples/ui-playground/src/app/playground/shadcn/page.tsx index 4a89b6f6..2e28f702 100644 --- a/examples/ui-playground/src/app/playground/shadcn/page.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/page.tsx @@ -3,12 +3,12 @@ import * as React from 'react' import Link from 'next/link' -import { Typography } from '@genseki/react' -import { linkVariants } from '@genseki/react/v2' +import { linkVariants, Typography } from '@genseki/react/v2' import { ButtonSection } from './button-section' import { CollapsibleSection } from './collapsible-section' import { ComboboxSection } from './combobox-section' +import { DatePickerSection } from './date-picker-section' import { DropdownMenuSection } from './dropdown-menu-section' import { InputSection } from './input-section' import { LinkSection } from './link-section' @@ -36,6 +36,11 @@ export default function ComboboxPage() { + + Date picker + + + Input @@ -101,6 +106,13 @@ export default function ComboboxPage() { {'>'} Combobox
+ + {'>'} Date picker + +
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 16bc966b..aa7ff4e7 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,7 +1,6 @@ import { MoonStarsIcon } from '@phosphor-icons/react' -import { Typography } from '@genseki/react' -import { SidebarMenu, SidebarMenuItem, SidebarTrigger } from '@genseki/react/v2' +import { SidebarMenu, SidebarMenuItem, SidebarTrigger, Typography } from '@genseki/react/v2' function NavHeader() { return ( 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 72cf2058..4db7eade 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 @@ -4,7 +4,6 @@ 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 +15,7 @@ import { SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, + Typography, } from '@genseki/react/v2' import { cn } from '../../../../../../../../packages/react/src/react/utils/cn' 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 23faafa7..4251e46f 100644 --- a/examples/ui-playground/src/app/playground/shadcn/sidebar/page.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/sidebar/page.tsx @@ -1,4 +1,4 @@ -'use client' +import { cookies as _cookies } from 'next/headers' import { Breadcrumb, @@ -15,13 +15,16 @@ import { import { AppSidebar } from './components/app-sidebar' -function DummySidebarPage() { +async function DummySidebarPage() { + const cookies = await _cookies() + const open = cookies.get('sidebar-state')?.value === 'true' + return ( - + -
-
+
+
@@ -40,11 +43,26 @@ function DummySidebarPage() {
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/ui-playground/src/app/playground/shadcn/textarea-section.tsx b/examples/ui-playground/src/app/playground/shadcn/textarea-section.tsx index a0620347..d9b987ef 100644 --- a/examples/ui-playground/src/app/playground/shadcn/textarea-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/textarea-section.tsx @@ -1,7 +1,6 @@ import React from 'react' -import { Typography } from '@genseki/react' -import { Label, Textarea } from '@genseki/react/v2' +import { Label, Textarea, Typography } from '@genseki/react/v2' import { PlaygroundCard } from '~/src/components/card' diff --git a/examples/ui-playground/src/app/playground/shadcn/toast-section.tsx b/examples/ui-playground/src/app/playground/shadcn/toast-section.tsx index 62af2b8a..18f5006a 100644 --- a/examples/ui-playground/src/app/playground/shadcn/toast-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/toast-section.tsx @@ -8,8 +8,7 @@ import { ShareIcon, } from '@phosphor-icons/react' -import { Typography } from '@genseki/react' -import { Button, toast } from '@genseki/react/v2' +import { Button, toast, Typography } from '@genseki/react/v2' import { PlaygroundCard } from '~/src/components/card' diff --git a/examples/ui-playground/src/app/playground/shadcn/toggle-group-section.tsx b/examples/ui-playground/src/app/playground/shadcn/toggle-group-section.tsx index 56f40c18..1eeee393 100644 --- a/examples/ui-playground/src/app/playground/shadcn/toggle-group-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/toggle-group-section.tsx @@ -17,8 +17,7 @@ import { TextUnderlineIcon, } from '@phosphor-icons/react' -import { Typography } from '@genseki/react' -import { ToggleGroup, ToggleGroupItem } from '@genseki/react/v2' +import { ToggleGroup, ToggleGroupItem, Typography } from '@genseki/react/v2' import { PlaygroundCard } from '~/src/components/card' @@ -97,12 +96,7 @@ function ToggleGroupSizes() { Medium - + Default Large @@ -122,13 +116,13 @@ function ToggleGroupWithIcons() { return (
- + - + - + diff --git a/examples/ui-playground/src/app/playground/shadcn/toggle-section.tsx b/examples/ui-playground/src/app/playground/shadcn/toggle-section.tsx index 6e8c2a97..6c8795b2 100644 --- a/examples/ui-playground/src/app/playground/shadcn/toggle-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/toggle-section.tsx @@ -14,8 +14,7 @@ import { TextUnderlineIcon, } from '@phosphor-icons/react' -import { Typography } from '@genseki/react' -import { Toggle } from '@genseki/react/v2' +import { Toggle, Typography } from '@genseki/react/v2' import { PlaygroundCard } from '~/src/components/card' @@ -63,7 +62,7 @@ function ToggleSizes() { Small - + Default @@ -81,13 +80,13 @@ function ToggleWithIcons() { return (
- + - + - +
@@ -122,18 +121,13 @@ function TextFormattingExample() { return (
- + @@ -141,7 +135,7 @@ function TextFormattingExample() { @@ -168,7 +162,7 @@ function AlignmentControls() { pressed && setAlignment('left')} - size="default" + size="md" variant="outline" > @@ -176,7 +170,7 @@ function AlignmentControls() { pressed && setAlignment('center')} - size="default" + size="md" variant="outline" > @@ -184,7 +178,7 @@ function AlignmentControls() { pressed && setAlignment('right')} - size="default" + size="md" variant="outline" > @@ -212,24 +206,16 @@ function FavoriteActions() { return (
- toggleFavorite('heart')} - size="default" - > + toggleFavorite('heart')} size="md"> - toggleFavorite('star')} - size="default" - > + toggleFavorite('star')} size="md"> toggleFavorite('bookmark')} - size="default" + size="md" > diff --git a/examples/ui-playground/src/app/playground/shadcn/tooltip-section.tsx b/examples/ui-playground/src/app/playground/shadcn/tooltip-section.tsx index fed62785..b51a5c67 100644 --- a/examples/ui-playground/src/app/playground/shadcn/tooltip-section.tsx +++ b/examples/ui-playground/src/app/playground/shadcn/tooltip-section.tsx @@ -8,8 +8,7 @@ import { WarningIcon, } from '@phosphor-icons/react' -import { Typography } from '@genseki/react' -import { Button } from '@genseki/react/v2' +import { Button, Typography } from '@genseki/react/v2' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@genseki/react/v2' import { PlaygroundCard } from '~/src/components/card' diff --git a/examples/ui-playground/src/components/card.tsx b/examples/ui-playground/src/components/card.tsx index ad74f913..1625a3bc 100644 --- a/examples/ui-playground/src/components/card.tsx +++ b/examples/ui-playground/src/components/card.tsx @@ -1,6 +1,6 @@ import { StarIcon } from '@phosphor-icons/react/dist/ssr' -import { Typography } from '@genseki/react' +import { Typography } from '@genseki/react/v2' type PlaygroundCardProps = React.PropsWithChildren<{ title: string diff --git a/examples/ui-playground/src/components/wrapper.tsx b/examples/ui-playground/src/components/wrapper.tsx index 310e7703..033770a2 100644 --- a/examples/ui-playground/src/components/wrapper.tsx +++ b/examples/ui-playground/src/components/wrapper.tsx @@ -1,4 +1,4 @@ -import { Typography } from '@genseki/react' +import { Typography } from '@genseki/react/v2' export const Wrapper = ({ children, title }: { children: React.ReactNode; title: string }) => { return ( diff --git a/packages/react/package.json b/packages/react/package.json index 8141c98d..2a4cfe24 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -86,6 +86,7 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "cookie-es": "^2.0.0", + "date-fns": "^4.1.0", "deepmerge-ts": "^7.1.5", "defu": "^6.1.4", "input-otp": "^1.4.2", @@ -110,6 +111,7 @@ "@types/react": "^19.1.6", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.3.4", + "react-day-picker": "^9.11.1", "tsup": "^8.5.0", "type-fest": "^4.38.0", "vite-tsconfig-paths": "^5.1.4", @@ -117,6 +119,7 @@ }, "peerDependencies": { "react": "^19.1.0", + "react-day-picker": "^9.11.1", "react-dom": "^19.1.0", "zod": "^4.0.11" } diff --git a/packages/react/src/auth/plugins/email-and-password/views/forgot-password/forgot-password.tsx b/packages/react/src/auth/plugins/email-and-password/views/forgot-password/forgot-password.tsx index 4fef74b8..7086f85e 100644 --- a/packages/react/src/auth/plugins/email-and-password/views/forgot-password/forgot-password.tsx +++ b/packages/react/src/auth/plugins/email-and-password/views/forgot-password/forgot-password.tsx @@ -1,4 +1,4 @@ -import { Typography } from '@genseki/react' +import { Typography } from '@genseki/react/v2' import { ForgotPasswordClientForm } from './forgot-password.client' diff --git a/packages/react/src/auth/plugins/email-and-password/views/reset-password/reset-password.tsx b/packages/react/src/auth/plugins/email-and-password/views/reset-password/reset-password.tsx index b94e6d4e..630ffabc 100644 --- a/packages/react/src/auth/plugins/email-and-password/views/reset-password/reset-password.tsx +++ b/packages/react/src/auth/plugins/email-and-password/views/reset-password/reset-password.tsx @@ -1,4 +1,4 @@ -import { Typography } from '@genseki/react' +import { Typography } from '@genseki/react/v2' import { ResetPasswordClientForm } from './reset-password.client' diff --git a/packages/react/src/react/components/primitives/calendar.tsx b/packages/react/src/react/components/primitives/calendar.tsx index d56d622e..b1ea549e 100644 --- a/packages/react/src/react/components/primitives/calendar.tsx +++ b/packages/react/src/react/components/primitives/calendar.tsx @@ -28,12 +28,18 @@ import { Select, SelectLabel, SelectList, SelectOption, SelectTrigger } from './ import { BaseIcon } from '../../components/primitives/base-icon' +/** + * @deprecated + */ interface CalendarProps extends Omit, 'visibleDuration'> { errorMessage?: string className?: string } +/** + * @deprecated + */ const Calendar = ({ errorMessage, className, ...props }: CalendarProps) => { const now = today(getLocalTimeZone()) @@ -70,6 +76,9 @@ const Calendar = ({ errorMessage, className, ...props }: Ca ) } +/** + * @deprecated + */ const CalendarHeader = ({ isRange, className, @@ -127,6 +136,9 @@ const CalendarHeader = ({ ) } +/** + * @deprecated + */ const SelectMonth = ({ state }: { state: CalendarState }) => { const months = [] @@ -161,6 +173,9 @@ const SelectMonth = ({ state }: { state: CalendarState }) => { ) } +/** + * @deprecated + */ const SelectYear = ({ state }: { state: CalendarState }) => { const years: { value: CalendarDate; formatted: string }[] = [] const formatter = useDateFormatter({ @@ -196,6 +211,9 @@ const SelectYear = ({ state }: { state: CalendarState }) => { ) } +/** + * @deprecated + */ const CalendarGridHeader = () => { return ( diff --git a/packages/react/src/react/components/primitives/typography.tsx b/packages/react/src/react/components/primitives/typography.tsx index 7f02a0ae..57ef1069 100644 --- a/packages/react/src/react/components/primitives/typography.tsx +++ b/packages/react/src/react/components/primitives/typography.tsx @@ -5,8 +5,14 @@ import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '../../utils/cn' +/** + * @deprecated + */ type TypographyTag = 'h1' | 'h2' | 'h3' | 'h4' | 'body' | 'label' | 'caption' +/** + * @deprecated + */ const nativeElementsMap: Record = { h1: 'h1', h2: 'h2', @@ -17,6 +23,9 @@ const nativeElementsMap: Record = { label: 'p', } +/** + * @deprecated + */ const typographyVariants = cva('inline-block', { variants: { weight: { @@ -37,6 +46,9 @@ const typographyVariants = cva('inline-block', { }, }) +/** + * @deprecated + */ interface TypographyProps extends React.HTMLAttributes, VariantProps { @@ -44,6 +56,9 @@ interface TypographyProps asChild?: boolean } +/** + * @deprecated + */ export const Typography = ({ type = 'body', weight = 'normal', diff --git a/packages/react/v2/components/primitives/breadcrumb.tsx b/packages/react/v2/components/primitives/breadcrumb.tsx index e8cac51e..a6a31af6 100644 --- a/packages/react/v2/components/primitives/breadcrumb.tsx +++ b/packages/react/v2/components/primitives/breadcrumb.tsx @@ -45,7 +45,10 @@ function BreadcrumbLink({ return ( ) @@ -58,7 +61,7 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) { role="link" aria-disabled="true" aria-current="page" - className={cn('text-foreground font-normal', className)} + className={cn('text-foreground text-sm font-normal p-2', className)} {...props} /> ) diff --git a/packages/react/v2/components/primitives/button.tsx b/packages/react/v2/components/primitives/button.tsx index 9b4904dc..f8db17d9 100644 --- a/packages/react/v2/components/primitives/button.tsx +++ b/packages/react/v2/components/primitives/button.tsx @@ -12,28 +12,28 @@ import { cn } from '../../../src/react/utils/cn' const buttonVariants = cva( [ "inline-flex items-center justify-center gap-4 whitespace-nowrap rounded-md text-base 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', + 'disabled:opacity-80 cursor-pointer ', ], { variants: { variant: { primary: - ' bg-surface-button-primary text-text-inverse hover:bg-surface-button-primary-hover active:bg-surface-button-primary disabled:bg-surface-button-primary-disabled disabled:text-text-disabled disabled:border disabled:border-border-button-primary-disabled', + ' bg-surface-button-primary text-text-inverse hover:bg-surface-button-primary-hover active:bg-surface-button-primary disabled:bg-surface-button-primary-disabled disabled:text-text-disabled disabled:*:text-text-disabled disabled:border disabled:border-border-button-primary-disabled', secondary: - ' bg-surface-button-secondary text-text-brand hover:bg-surface-button-secondary-hover active:bg-surface-button-secondary disabled:bg-surface-button-secondary-disabled disabled:text-text-disabled', + ' bg-surface-button-secondary text-text-brand hover:bg-surface-button-secondary-hover active:bg-surface-button-secondary disabled:bg-surface-button-secondary-disabled disabled:text-text-disabled disabled:*:text-text-disabled', tertiary: - ' bg-surface-button-tertiary border border-border-button-tertiary text-text-brand hover:bg-surface-button-tertiary-hover active:bg-surface-button-tertiary hover:border-border-button-tertiary-hover disabled:bg-surface-button-tertiary-disabled disabled:border-border-button-tertiary-disabled disabled:text-text-disabled', + ' bg-surface-button-tertiary border border-border-button-tertiary text-text-brand hover:bg-surface-button-tertiary-hover active:bg-surface-button-tertiary hover:border-border-button-tertiary-hover disabled:bg-surface-button-tertiary-disabled disabled:border-border-button-tertiary-disabled disabled:text-text-disabled disabled:*:text-text-disabled', naked: - 'shadow-sm ring-offset-0 text-text-secondary hover:bg-surface-button-naked-hover active:bg-transparent disabled:bg-surface-button-naked-disabled disabled:text-text-disabled', + 'shadow-sm ring-offset-0 text-text-secondary hover:bg-surface-button-naked-hover active:bg-transparent disabled:bg-surface-button-naked-disabled disabled:text-text-disabled disabled:*:text-text-disabled', outline: - ' border border-border-button-outline text-text-secondary bg-surface-button-outline shadow-xs hover:bg-surface-button-outline-hover active:bg-surface-button-outline hover:border-border-button-outline-hover disabled:bg-surface-button-outline-disabled disabled:text-text-disabled disabled:border disabled:border-border-button-outline-disabled dark:bg-input/30 dark:border-input dark:hover:bg-input/50', + ' border border-border-button-outline text-text-secondary bg-surface-button-outline shadow-xs hover:bg-surface-button-outline-hover active:bg-surface-button-outline hover:border-border-button-outline-hover disabled:bg-surface-button-outline-disabled disabled:text-text-disabled disabled:*:text-text-disabled disabled:border disabled:border-border-button-outline-disabled dark:bg-input/30 dark:border-input dark:hover:bg-input/50', ghost: 'bg-transparent 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 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', }, size: { - default: 'h-18 px-8 py-4 has-[>svg]:px-6', + md: 'h-18 px-8 py-4 has-[>svg]:px-6', sm: 'h-16 rounded-md gap-3 px-6 has-[>svg]:px-5', lg: 'h-20 rounded-md px-12 has-[>svg]:px-8', icon: 'size-18', @@ -43,7 +43,7 @@ const buttonVariants = cva( }, defaultVariants: { variant: 'primary', - size: 'default', + size: 'md', }, } ) diff --git a/packages/react/v2/components/primitives/calendar.tsx b/packages/react/v2/components/primitives/calendar.tsx new file mode 100644 index 00000000..69d9005a --- /dev/null +++ b/packages/react/v2/components/primitives/calendar.tsx @@ -0,0 +1,229 @@ +'use client' + +import * as React from 'react' +import { type DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker' + +import { CaretDownIcon, CaretLeftIcon, CaretRightIcon } from '@phosphor-icons/react' + +import { Button, buttonVariants } from './button' + +import { cn } from '../../../src/react/utils/cn' + +function Calendar({ + className, + classNames, + showOutsideDays = true, + captionLayout = 'label', + buttonVariant = 'ghost', + formatters, + components, + ...props +}: React.ComponentProps & { + buttonVariant?: React.ComponentProps['variant'] +}) { + const defaultClassNames = getDefaultClassNames() + + return ( + svg]:rotate-180`, + String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, + className + )} + captionLayout={captionLayout} + formatters={{ + formatMonthDropdown: (date) => date.toLocaleString('default', { month: 'short' }), + ...formatters, + }} + classNames={{ + ...classNames, + root: cn('w-fit', defaultClassNames.root, classNames?.root), + months: cn( + 'flex gap-8 flex-col md:flex-row relative', + defaultClassNames.months, + classNames?.months + ), + month: cn('flex flex-col w-full gap-8', defaultClassNames.month, classNames?.month), + nav: cn( + 'flex items-center gap-2 w-full absolute top-0 inset-x-0 justify-between', + defaultClassNames.nav, + classNames?.nav + ), + button_previous: cn( + buttonVariants({ variant: buttonVariant }), + 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none', + defaultClassNames.button_previous, + classNames?.button_previous + ), + button_next: cn( + buttonVariants({ variant: buttonVariant }), + 'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none', + defaultClassNames.button_next, + classNames?.button_next + ), + month_caption: cn( + 'flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)', + defaultClassNames.month_caption, + classNames?.month_caption + ), + dropdowns: cn( + 'w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-3', + defaultClassNames.dropdowns, + classNames?.dropdowns + ), + dropdown_root: cn( + 'relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring has-focus:ring-[2px] rounded-md', + defaultClassNames.dropdown_root, + classNames?.dropdown_root + ), + dropdown: cn( + 'absolute bg-popover inset-0 opacity-0', + defaultClassNames.dropdown, + classNames?.dropdown + ), + caption_label: cn( + 'select-none font-medium', + captionLayout === 'label' + ? 'text-sm' + : 'rounded-md pl-4 pr-2 flex items-center gap-2 text-sm h-16 [&>svg]:text-muted-foreground [&>svg]:size-7', + defaultClassNames.caption_label, + classNames?.caption_label + ), + month_grid: cn('w-full border-collapse', classNames?.month_grid), + weekdays: cn('flex', defaultClassNames.weekdays, classNames?.weekdays), + weekday: cn( + 'text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none', + defaultClassNames.weekday, + classNames?.weekday + ), + week: cn('flex w-full mt-4', defaultClassNames.week, classNames?.week), + week_number_header: cn( + 'select-none w-(--cell-size)', + defaultClassNames.week_number_header, + classNames?.week_number_header + ), + week_number: cn( + 'text-[0.8rem] select-none text-muted-foreground', + defaultClassNames.week_number, + classNames?.week_number + ), + day: cn( + 'relative w-full h-full p-0 text-center group/day aspect-square select-none', + defaultClassNames.day, + classNames?.day + ), + range_start: cn( + 'rounded-l-full bg-accent', + defaultClassNames.range_start, + classNames?.range_start + ), + range_middle: cn('rounded-none', defaultClassNames.range_middle, classNames?.range_middle), + range_end: cn( + 'rounded-r-full bg-accent', + defaultClassNames.range_end, + classNames?.range_end + ), + today: cn( + 'bg-accent text-accent-foreground rounded-full data-[selected=true]:rounded-full', + 'before:bg-primary before:content-[""] before:absolute relative before:inset-x-0 before:size-2 before:rounded-full before:bottom-1.5 before:mx-auto', // Dot + defaultClassNames.today, + classNames?.today + ), + outside: cn( + 'text-muted-foreground aria-selected:text-muted-foreground', + defaultClassNames.outside, + classNames?.outside + ), + disabled: cn( + 'text-muted-foreground opacity-50', + defaultClassNames.disabled, + classNames?.disabled + ), + hidden: cn('invisible', defaultClassNames.hidden, classNames?.hidden), + }} + components={{ + Root: ({ className, rootRef, ...props }) => { + return ( +
| undefined} + className={cn(className)} + {...props} + /> + ) + }, + Chevron: ({ className, orientation, ...props }) => { + if (orientation === 'left') { + return + } + + if (orientation === 'right') { + return + } + + return + }, + DayButton: CalendarDayButton, + WeekNumber: ({ children, ...props }) => { + return ( + +
+ {children} +
+ + ) + }, + ...components, + }} + {...props} + /> + ) +} + +function CalendarDayButton({ + className, + day, + modifiers, + ...props +}: React.ComponentProps) { + const defaultClassNames = getDefaultClassNames() + + const ref = React.useRef(null) + React.useEffect(() => { + if (modifiers.focused) ref.current?.focus() + }, [modifiers.focused]) + + return ( + + )} + + ) +} + +export function DatePickerContent({ + className, + ...props +}: React.ComponentPropsWithRef) { + return +} diff --git a/packages/react/v2/components/primitives/index.ts b/packages/react/v2/components/primitives/index.ts index 37ba3732..b935b224 100644 --- a/packages/react/v2/components/primitives/index.ts +++ b/packages/react/v2/components/primitives/index.ts @@ -1,9 +1,11 @@ export * from './breadcrumb' export * from './button' export * from './button-group' +export * from './calendar' export * from './collapsible' export * from './combobox' export * from './command' +export * from './date-picker' export * from './dialog' export * from './dropdown-menu' export * from './input' @@ -20,3 +22,4 @@ export * from './textarea' export * from './toggle' export * from './toggle-group' export * from './tooltip' +export * from './typography' diff --git a/packages/react/v2/components/primitives/input-group.tsx b/packages/react/v2/components/primitives/input-group.tsx index 76976698..6b1958f5 100644 --- a/packages/react/v2/components/primitives/input-group.tsx +++ b/packages/react/v2/components/primitives/input-group.tsx @@ -8,46 +8,56 @@ import { cva, type VariantProps } from 'class-variance-authority' import { Button } from './button' import type { Input } from './input' +import { createRequiredContext } from '../../../src/react/hooks/create-required-context' import { cn } from '../../../src/react/utils/cn' +const [InputGroupProvider, useInputGroup] = createRequiredContext<{ + isPending?: boolean + disabled?: boolean +}>('Input group context') + function InputGroup({ className, isPending, + disabled, ...props -}: React.ComponentProps<'div'> & { isPending?: boolean }) { +}: React.ComponentProps<'div'> & { isPending?: boolean; disabled?: boolean }) { return ( -
textarea]:h-auto', - - // Variants based on alignment. - 'has-[>[data-align=inline-start]]:[&>input]:pl-4', - 'has-[>[data-align=inline-end]]:[&>input]:pr-4', - 'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-6', - 'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-6', - - // Focus state. - 'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[2px]', - - // Error state. - 'has-[[data-slot][aria-invalid=true]]:ring-destructive has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive', - - // Custom - isPending && 'bg-muted pointer-events-none animate-pulse', - - className - )} - {...props} - /> + +
textarea]:h-auto', + + // Variants based on alignment. + 'has-[>[data-align=inline-start]]:[&>input]:pl-4', + 'has-[>[data-align=inline-end]]:[&>input]:pr-4', + 'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-6', + 'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-6', + + // Focus state. + 'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[2px]', + + // Error state. + 'has-[[data-slot][aria-invalid=true]]:ring-destructive has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive', + + // Custom + isPending && 'bg-muted pointer-events-none animate-pulse', + + className + )} + {...props} + /> + ) } const inputGroupAddonVariants = cva( - "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-4 py-3 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-8 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50", + "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-4 py-3 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-8 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-80", { variants: { align: { @@ -87,19 +97,25 @@ function InputGroupAddon({ ) } -const inputGroupButtonVariants = cva('text-sm shadow-none flex gap-2 items-center', { - variants: { - size: { - xs: "h-12 gap-2 px-4 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-7 has-[>svg]:px-4", - sm: 'h-16 px-5 gap-3 rounded-md has-[>svg]:px-5', - 'icon-xs': 'size-12 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0', - 'icon-sm': 'size-16 p-0 has-[>svg]:p-0', +const inputGroupButtonVariants = cva( + [ + 'text-sm shadow-none flex gap-2 items-center ring-offset-0', + 'group-data-[disabled=true]/input-group:pointer-events-none', + ], + { + variants: { + size: { + xs: "h-12 gap-2 px-4 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-7 has-[>svg]:px-4", + sm: 'h-16 px-5 gap-3 rounded-md has-[>svg]:px-5', + 'icon-xs': 'size-12 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0', + 'icon-sm': 'size-16 p-0 has-[>svg]:p-0', + }, }, - }, - defaultVariants: { - size: 'xs', - }, -}) + defaultVariants: { + size: 'xs', + }, + } +) function InputGroupButton({ className, @@ -109,11 +125,13 @@ function InputGroupButton({ ...props }: Omit, 'size'> & VariantProps) { + const ctx = useInputGroup() return (