Files
simstudioai--sim/packages/emcn/src/components/chip-date-picker/chip-date-picker.tsx
T
wehub-resource-sync d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

177 lines
6.1 KiB
TypeScript

'use client'
import { forwardRef, useState } from 'react'
import * as PopoverPrimitive from '@radix-ui/react-popover'
import { ChevronDown } from '../../icons'
import { cn } from '../../lib/cn'
import { Calendar, formatDateLabel, formatDateRangeLabel } from '../calendar/calendar'
import { chipVariants, TRIGGER_BORDER_CLASS } from '../chip/chip'
import { chipContentLabelClass } from '../chip/chip-chrome'
import { POPOVER_ANIMATION_CLASSES } from '../popover/popover-animation'
interface ChipDatePickerBaseProps {
/**
* Trigger chrome. `filled` (default) is the bordered field chip with the owned
* chevron; `ghost` is the bare toolbar pill — no border, no chevron — for
* visual parity with neighboring `Chip` buttons.
*/
variant?: 'filled' | 'ghost'
/**
* Overrides the trigger text, e.g. a calendar period label ("June 2026") whose
* granularity differs from the selected value.
*/
label?: string
/** Shown in the trigger when nothing is selected. */
placeholder?: string
/** Aligns the calendar popover relative to the trigger. */
align?: 'start' | 'center' | 'end'
/** Disables the trigger. */
disabled?: boolean
/** Stretch the trigger to fill its container (mirrors `Chip`'s `fullWidth`). */
fullWidth?: boolean
/** Removes the default `mx-0.5` cluster margin (mirrors `Chip`'s `flush`). */
flush?: boolean
/** Forwarded class for the trigger button. */
className?: string
}
interface ChipDatePickerSingleProps extends ChipDatePickerBaseProps {
mode?: 'single'
/** Selected date as a `YYYY-MM-DD` string. */
value?: string
/** Called with the picked date in `YYYY-MM-DD` format. */
onChange?: (value: string) => void
/**
* Today's calendar day (`YYYY-MM-DD`) in the caller's effective timezone;
* defaults to the runtime's local day (mirrors `Calendar`'s `today`).
*/
today?: string
}
interface ChipDatePickerRangeProps extends ChipDatePickerBaseProps {
mode: 'range'
/** Range start as a `YYYY-MM-DD` (or `YYYY-MM-DDTHH:mm`) string. */
startDate?: string
/** Range end as a `YYYY-MM-DD` (or `YYYY-MM-DDTHH:mm`) string. */
endDate?: string
/** Adds start/end time-of-day inputs; emits `YYYY-MM-DDTHH:mm` bounds. */
showTime?: boolean
/** Called on Apply with the ordered range bounds. */
onRangeChange: (start: string, end: string) => void
}
export type ChipDatePickerProps = ChipDatePickerSingleProps | ChipDatePickerRangeProps
/**
* Date counterpart to {@link ChipDropdown} — a chip-styled trigger that opens a
* {@link Calendar} in a popover. The default `filled` trigger reuses
* `chipVariants` (filled + border) and the owned chevron for visual parity with
* the other chip field controls; `ghost` renders the bare toolbar pill instead.
*
* `mode='single'` (default) commits on day click. `mode='range'` opens the
* range calendar — start/end staged behind Clear/Cancel/Apply, with optional
* time-of-day inputs — and commits via `onRangeChange`.
*
* @example
* <ChipDatePicker value={value} onChange={setValue} placeholder='Select date' fullWidth />
*
* @example
* <ChipDatePicker mode='range' startDate={from} endDate={to} showTime onRangeChange={apply} />
*/
const ChipDatePicker = forwardRef<HTMLButtonElement, ChipDatePickerProps>(
function ChipDatePicker(props, ref) {
const {
variant = 'filled',
label,
placeholder = props.mode === 'range' ? 'Select date range' : 'Select date',
align = 'start',
disabled,
fullWidth,
flush,
className,
} = props
const [open, setOpen] = useState(false)
const triggerText =
label ??
(props.mode === 'range'
? formatDateRangeLabel(props.startDate, props.endDate)
: formatDateLabel(props.value))
return (
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
<PopoverPrimitive.Trigger asChild disabled={disabled}>
<button
ref={ref}
type='button'
disabled={disabled}
className={cn(
variant === 'ghost'
? chipVariants({ fullWidth, flush })
: cn(chipVariants({ variant: 'filled', fullWidth, flush }), TRIGGER_BORDER_CLASS),
className
)}
>
<span
className={cn(
chipContentLabelClass,
'flex-1',
!triggerText && 'text-[var(--text-muted)]'
)}
>
{triggerText || placeholder}
</span>
{variant === 'filled' && (
<span
aria-hidden
className='inline-flex size-[16px] flex-shrink-0 items-center justify-center text-[var(--text-icon)]'
>
<ChevronDown className='h-[6px] w-[10px]' />
</span>
)}
</button>
</PopoverPrimitive.Trigger>
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
align={align}
sideOffset={6}
collisionPadding={8}
className={cn(
POPOVER_ANIMATION_CLASSES,
'z-[var(--z-popover)] origin-[--radix-popover-content-transform-origin] rounded-xl border border-[var(--border-1)] bg-[var(--bg)] shadow-sm'
)}
>
{props.mode === 'range' ? (
<Calendar
mode='range'
startDate={props.startDate}
endDate={props.endDate}
showTime={props.showTime}
onRangeChange={(start, end) => {
props.onRangeChange(start, end)
setOpen(false)
}}
onCancel={() => setOpen(false)}
/>
) : (
<Calendar
value={props.value}
today={props.today}
onChange={(next) => {
props.onChange?.(next)
setOpen(false)
}}
/>
)}
</PopoverPrimitive.Content>
</PopoverPrimitive.Portal>
</PopoverPrimitive.Root>
)
}
)
ChipDatePicker.displayName = 'ChipDatePicker'
export { ChipDatePicker }