feat(picker-shell): extend to Root + Header + Body + Footer layout

Promote PickerShell from action-row-only to a canonical 4-zone shell
(Root → Header / Body / Footer) shared by every composite picker so
date/time/color popovers get the same visual contract for free.

- Add `<PickerShell.Root>` flex-column wrapper (emits data-picker-shell,
  data-mode) plus `<PickerShell.Header>` and `<PickerShell.Body>` zones
  matching the existing Footer's spacing + border tokens.
- Auto-wrap all 5 *-content.svelte popovers (date / date-range / time /
  time-range / color) in `<PickerShell.Root>` so consumers only compose
  Body + Footer.
- Refresh all 5 demos to use `<PickerShell.Body>` around picker content;
  date-picker demo gains a `showShellHeader` toggle showcasing the new
  Header zone.
- CSS scales section gap + header spacing across xs/sm/md/lg picker
  sizes via [data-popover-content][data-picker-size] scopes, in sync
  with the existing Footer scale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent da10548a68
commit afb09c2254

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ActiveEidos } from '$uix/eidos'; import { ActiveEidos } from '$uix/eidos';
import PopoverContent from '../popover/popover-content.svelte'; import PopoverContent from '../popover/popover-content.svelte';
import PickerShellRoot from '../picker-shell/picker-shell.svelte';
import { getColorPickerVisualContext } from './context.svelte'; import { getColorPickerVisualContext } from './context.svelte';
import type { ColorPickerContentProps } from './types'; import type { ColorPickerContentProps } from './types';
@ -28,6 +29,8 @@
data-color-picker-content="" data-color-picker-content=""
> >
{#snippet children(snippetProps)} {#snippet children(snippetProps)}
<PickerShellRoot>
{@render bodyContent?.(snippetProps)} {@render bodyContent?.(snippetProps)}
</PickerShellRoot>
{/snippet} {/snippet}
</PopoverContent> </PopoverContent>

@ -2,6 +2,7 @@
import { ActiveEidos } from '$uix/eidos'; import { ActiveEidos } from '$uix/eidos';
import { composeInlineStyle } from '$uix/eidos/lib/style'; import { composeInlineStyle } from '$uix/eidos/lib/style';
import PopoverContent from '../popover/popover-content.svelte'; import PopoverContent from '../popover/popover-content.svelte';
import PickerShellRoot from '../picker-shell/picker-shell.svelte';
import { getDatePickerVisualContext } from './context.svelte'; import { getDatePickerVisualContext } from './context.svelte';
import type { DatePickerContentProps } from './types'; import type { DatePickerContentProps } from './types';
@ -71,6 +72,8 @@
style={composedStyle} style={composedStyle}
> >
{#snippet children(snippetProps)} {#snippet children(snippetProps)}
<PickerShellRoot>
{@render bodyContent?.(snippetProps)} {@render bodyContent?.(snippetProps)}
</PickerShellRoot>
{/snippet} {/snippet}
</PopoverContent> </PopoverContent>

@ -2,6 +2,7 @@
import { ActiveEidos } from '$uix/eidos'; import { ActiveEidos } from '$uix/eidos';
import { composeInlineStyle } from '$uix/eidos/lib/style'; import { composeInlineStyle } from '$uix/eidos/lib/style';
import PopoverContent from '../popover/popover-content.svelte'; import PopoverContent from '../popover/popover-content.svelte';
import PickerShellRoot from '../picker-shell/picker-shell.svelte';
import { getDateRangePickerVisualContext } from './context.svelte'; import { getDateRangePickerVisualContext } from './context.svelte';
import type { DateRangePickerContentProps } from './types'; import type { DateRangePickerContentProps } from './types';
@ -71,6 +72,8 @@
style={composedStyle} style={composedStyle}
> >
{#snippet children(snippetProps)} {#snippet children(snippetProps)}
<PickerShellRoot>
{@render bodyContent?.(snippetProps)} {@render bodyContent?.(snippetProps)}
</PickerShellRoot>
{/snippet} {/snippet}
</PopoverContent> </PopoverContent>

@ -1,41 +1,64 @@
import PickerShellRoot from './picker-shell.svelte'
import Header from './picker-shell-header.svelte'
import Body from './picker-shell-body.svelte'
import Footer from './picker-shell-footer.svelte' import Footer from './picker-shell-footer.svelte'
import Clear from './picker-shell-clear.svelte' import Clear from './picker-shell-clear.svelte'
import Cancel from './picker-shell-cancel.svelte' import Cancel from './picker-shell-cancel.svelte'
import Close from './picker-shell-close.svelte' import Close from './picker-shell-close.svelte'
/** /**
* PickerShell — generic Footer + action row shared by every composite * PickerShell — canonical 4-zone layout (Root → Header / Body / Footer)
* picker that registers a `PickerShellHandle` via `pickerShellContext`. * + action row shared by every composite picker that registers a
* `PickerShellHandle` via `pickerShellContext`.
* *
* import { PickerShell } from '$uix/eidos/components/picker-shell'; * import { PickerShell } from '$uix/eidos/components/picker-shell';
* *
* <PickerShell>
* <PickerShell.Header>...</PickerShell.Header>
* <PickerShell.Body>
* <DatePicker.Calendar>...</DatePicker.Calendar>
* </PickerShell.Body>
* <PickerShell.Footer> * <PickerShell.Footer>
* <PickerShell.Clear /> * <PickerShell.Clear />
* <PickerShell.Cancel /> * <PickerShell.Cancel />
* <PickerShell.Close /> * <PickerShell.Close />
* </PickerShell.Footer> * </PickerShell.Footer>
* </PickerShell>
* *
* Each composite picker re-exports these as `X.Footer / X.Clear / …` * Header is optional. Footer / Clear / Cancel / Close are also exposed
* for namespace consistency: * under each composite picker's namespace for symmetry
* * (`<DatePicker.Footer>`, `<ColorPicker.Clear>`, …).
* <ColorPicker.Footer>
* <ColorPicker.Clear />
* <ColorPicker.Cancel />
* <ColorPicker.Close />
* </ColorPicker.Footer>
*
* is the same render — both reach the host picker through context.
*/ */
export const PickerShell = {
Footer, type PickerShellNamespace = typeof PickerShellRoot & {
Clear, Root: typeof PickerShellRoot
Cancel, Header: typeof Header
Close Body: typeof Body
Footer: typeof Footer
Clear: typeof Clear
Cancel: typeof Cancel
Close: typeof Close
} }
export { Footer, Clear, Cancel, Close } const PickerShell = PickerShellRoot as PickerShellNamespace
PickerShell.Root = PickerShellRoot
PickerShell.Header = Header
PickerShell.Body = Body
PickerShell.Footer = Footer
PickerShell.Clear = Clear
PickerShell.Cancel = Cancel
PickerShell.Close = Close
export { PickerShell }
export default PickerShell
export { PickerShellRoot as Root, Header, Body, Footer, Clear, Cancel, Close }
export type { export type {
PickerShellProps as RootProps,
PickerShellProps,
PickerShellHeaderProps as HeaderProps,
PickerShellBodyProps as BodyProps,
PickerShellFooterProps as FooterProps, PickerShellFooterProps as FooterProps,
PickerShellClearProps as ClearProps, PickerShellClearProps as ClearProps,
PickerShellCancelProps as CancelProps, PickerShellCancelProps as CancelProps,

@ -0,0 +1,12 @@
<script lang="ts">
import type { PickerShellBodyProps } from './types';
// Body zone — the scrollable middle area that contains the
// picker-specific content (calendar / clock / color area + sliders).
// Pure layout, no behaviour.
let { children, ...rest }: PickerShellBodyProps = $props();
</script>
<div data-picker-body {...rest}>
{@render children?.()}
</div>

@ -0,0 +1,12 @@
<script lang="ts">
import type { PickerShellHeaderProps } from './types';
// Header zone — optional top strip inside a picker popover. Mirrors
// the Footer's bottom border by emitting a bottom-border + spacing
// block via [data-picker-header] CSS. Pure layout, no behaviour.
let { children, ...rest }: PickerShellHeaderProps = $props();
</script>
<div data-picker-header {...rest}>
{@render children?.()}
</div>

@ -1,12 +1,51 @@
/* PickerShell — visual layer for the canonical Footer + action row /* PickerShell — visual layer for the canonical 4-zone layout
* shared by every composite picker (date, time, color, range). * (Root → Header / Body / Footer) + action row shared by every
* composite picker (date, time, color, range).
* *
* The host picker scopes us via the parent `[data-{x}-picker]` / * The host picker scopes us via the parent `[data-{x}-picker]` /
* `[data-{x}-picker-content]` element, but the action selectors * `[data-{x}-picker-content]` element, but the layout selectors
* (`[data-picker-footer/clear/cancel/close]`) are intentionally generic * (`[data-picker-shell/header/body/footer/clear/cancel/close]`) are
* so every picker gets the same visual contract for free. * intentionally generic so every picker gets the same visual contract
* for free.
*/ */
[data-picker-shell] {
/* Outer column wrapper. Size-scaling vars default to `sm`; host
* pickers re-tune via `[data-popover-content][data-picker-size='X']`
* scopes below. */
--_picker-shell-section-gap: var(--space-3);
display: flex;
flex-direction: column;
gap: var(--_picker-shell-section-gap);
min-block-size: 0;
}
[data-picker-header] {
/* Mirror the Footer's bottom border by emitting a bottom-border +
* spacing block. Reuses the same `--_picker-shell-row-spacing` token
* so xs/sm/md/lg scale symmetrically with the action row below. */
--_picker-shell-row-spacing: var(--space-3);
display: flex;
align-items: center;
gap: var(--space-2);
padding-block-end: var(--_picker-shell-row-spacing);
border-block-end: var(--border-width) solid var(--color-border-subtle);
color: var(--color-content-primary);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
line-height: var(--leading-ui);
}
[data-picker-body] {
display: flex;
flex-direction: column;
gap: var(--space-3);
flex: 1 1 auto;
min-block-size: 0;
}
[data-picker-footer] { [data-picker-footer] {
/* Size-scaling vars. Defaults match `sm`. The host picker scopes these /* Size-scaling vars. Defaults match `sm`. The host picker scopes these
* via `[data-popover-content][data-picker-size='X']` so xs/md/lg tune * via `[data-popover-content][data-picker-size='X']` so xs/md/lg tune
@ -21,10 +60,15 @@
align-items: center; align-items: center;
gap: var(--_picker-shell-gap); gap: var(--_picker-shell-gap);
padding-block-start: var(--_picker-shell-row-spacing); padding-block-start: var(--_picker-shell-row-spacing);
margin-block-start: var(--_picker-shell-row-spacing);
border-block-start: var(--border-width) solid var(--color-border-subtle); border-block-start: var(--border-width) solid var(--color-border-subtle);
} }
/* Standalone Footer (legacy composition without Root) keeps its own
* top margin so it still feels separated from picker content above. */
[data-picker-footer]:not([data-picker-shell] > *) {
margin-block-start: var(--_picker-shell-row-spacing);
}
[data-picker-clear], [data-picker-clear],
[data-picker-cancel] { [data-picker-cancel] {
margin-inline-end: auto; margin-inline-end: auto;
@ -51,9 +95,17 @@
} }
/* Size variants — host pickers emit `data-picker-size` on the popover /* Size variants — host pickers emit `data-picker-size` on the popover
* content so portaled descendants can scale. The button row inherits * content so portaled descendants can scale. The whole shell (gap,
* the picker's size; popover chrome (padding / min-width) keeps its * header spacing, button row) inherits the picker's size; popover
* own size variant via [data-size]. */ * chrome (padding / min-width) keeps its own size variant via
* [data-size]. */
[data-popover-content][data-picker-size='xs'] [data-picker-shell] {
--_picker-shell-section-gap: var(--space-2);
}
[data-popover-content][data-picker-size='xs'] [data-picker-header] {
--_picker-shell-row-spacing: var(--space-2);
font-size: var(--font-size-xs);
}
[data-popover-content][data-picker-size='xs'] [data-picker-footer] { [data-popover-content][data-picker-size='xs'] [data-picker-footer] {
--_picker-shell-control-h: var(--control-height-xxs); --_picker-shell-control-h: var(--control-height-xxs);
--_picker-shell-padding-x: var(--space-2); --_picker-shell-padding-x: var(--space-2);
@ -62,12 +114,25 @@
--_picker-shell-row-spacing: var(--space-2); --_picker-shell-row-spacing: var(--space-2);
} }
[data-popover-content][data-picker-size='md'] [data-picker-shell] {
--_picker-shell-section-gap: var(--space-3-5);
}
[data-popover-content][data-picker-size='md'] [data-picker-header] {
font-size: var(--font-size-md);
}
[data-popover-content][data-picker-size='md'] [data-picker-footer] { [data-popover-content][data-picker-size='md'] [data-picker-footer] {
--_picker-shell-control-h: var(--control-height-md); --_picker-shell-control-h: var(--control-height-md);
--_picker-shell-padding-x: var(--space-3-5); --_picker-shell-padding-x: var(--space-3-5);
--_picker-shell-font: var(--font-size-md); --_picker-shell-font: var(--font-size-md);
} }
[data-popover-content][data-picker-size='lg'] [data-picker-shell] {
--_picker-shell-section-gap: var(--space-4);
}
[data-popover-content][data-picker-size='lg'] [data-picker-header] {
--_picker-shell-row-spacing: var(--space-4);
font-size: var(--font-size-md);
}
[data-popover-content][data-picker-size='lg'] [data-picker-footer] { [data-popover-content][data-picker-size='lg'] [data-picker-footer] {
--_picker-shell-control-h: var(--control-height-lg); --_picker-shell-control-h: var(--control-height-lg);
--_picker-shell-padding-x: var(--space-4); --_picker-shell-padding-x: var(--space-4);

@ -0,0 +1,17 @@
<script lang="ts">
import { getPickerShellHandle } from '$soma/components/picker-shell';
import type { PickerShellProps } from './types';
// PickerShell root — outer flex column wrapping the canonical
// Header / Body / Footer zones for every composite picker. Pure
// composition; reads `mode` off `pickerShellContext` when available so
// the CSS can react to inline vs modal.
let { children, ...rest }: PickerShellProps = $props();
const shell = getPickerShellHandle();
const mode = $derived(shell?.getMode() ?? 'inline');
</script>
<div data-picker-shell data-mode={mode} {...rest}>
{@render children?.()}
</div>

@ -1,6 +1,21 @@
import type { Snippet } from 'svelte' import type { Snippet } from 'svelte'
import type { HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements' import type { HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements'
/** Props for `<PickerShell>` / `<PickerShell.Root>` — outer 4-zone layout. */
export type PickerShellProps = HTMLAttributes<HTMLDivElement> & {
children?: Snippet
}
/** Props for `<PickerShell.Header>`. */
export type PickerShellHeaderProps = HTMLAttributes<HTMLDivElement> & {
children?: Snippet
}
/** Props for `<PickerShell.Body>`. */
export type PickerShellBodyProps = HTMLAttributes<HTMLDivElement> & {
children?: Snippet
}
/** Props for `<PickerShell.Footer>`. */ /** Props for `<PickerShell.Footer>`. */
export type PickerShellFooterProps = HTMLAttributes<HTMLDivElement> & { export type PickerShellFooterProps = HTMLAttributes<HTMLDivElement> & {
children?: Snippet children?: Snippet

@ -2,6 +2,7 @@
import { ActiveEidos } from '$uix/eidos'; import { ActiveEidos } from '$uix/eidos';
import { composeInlineStyle } from '$uix/eidos/lib/style'; import { composeInlineStyle } from '$uix/eidos/lib/style';
import PopoverContent from '../popover/popover-content.svelte'; import PopoverContent from '../popover/popover-content.svelte';
import PickerShellRoot from '../picker-shell/picker-shell.svelte';
import { getTimePickerVisualContext } from './context.svelte'; import { getTimePickerVisualContext } from './context.svelte';
import type { TimePickerContentProps } from './types'; import type { TimePickerContentProps } from './types';
@ -67,6 +68,8 @@
style={composedStyle} style={composedStyle}
> >
{#snippet children(snippetProps)} {#snippet children(snippetProps)}
<PickerShellRoot>
{@render bodyContent?.(snippetProps)} {@render bodyContent?.(snippetProps)}
</PickerShellRoot>
{/snippet} {/snippet}
</PopoverContent> </PopoverContent>

@ -2,6 +2,7 @@
import { ActiveEidos } from '$uix/eidos'; import { ActiveEidos } from '$uix/eidos';
import { composeInlineStyle } from '$uix/eidos/lib/style'; import { composeInlineStyle } from '$uix/eidos/lib/style';
import PopoverContent from '../popover/popover-content.svelte'; import PopoverContent from '../popover/popover-content.svelte';
import PickerShellRoot from '../picker-shell/picker-shell.svelte';
import { getTimeRangePickerVisualContext } from './context.svelte'; import { getTimeRangePickerVisualContext } from './context.svelte';
import type { TimeRangePickerContentProps } from './types'; import type { TimeRangePickerContentProps } from './types';
@ -67,6 +68,8 @@
style={composedStyle} style={composedStyle}
> >
{#snippet children(snippetProps)} {#snippet children(snippetProps)}
<PickerShellRoot>
{@render bodyContent?.(snippetProps)} {@render bodyContent?.(snippetProps)}
</PickerShellRoot>
{/snippet} {/snippet}
</PopoverContent> </PopoverContent>

@ -6,6 +6,7 @@
type ColorPickerVariant, type ColorPickerVariant,
type ChannelInputSnippetProps type ChannelInputSnippetProps
} from '$uix/eidos/components/color-picker'; } from '$uix/eidos/components/color-picker';
import { PickerShell } from '$uix/eidos/components/picker-shell';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { colorPickerMorfo } from '@/uix/morfo/components/color-picker'; import { colorPickerMorfo } from '@/uix/morfo/components/color-picker';
import { colorFieldMorfo } from '@/uix/morfo/components/color-field'; import { colorFieldMorfo } from '@/uix/morfo/components/color-field';
@ -149,6 +150,7 @@
' <ColorPicker.Trigger><ColorPicker.ValueText /></ColorPicker.Trigger>', ' <ColorPicker.Trigger><ColorPicker.ValueText /></ColorPicker.Trigger>',
' </ColorPicker.Control>', ' </ColorPicker.Control>',
' <ColorPicker.Content>', ' <ColorPicker.Content>',
' <PickerShell.Body>',
' <ColorPicker.Area>', ' <ColorPicker.Area>',
' <ColorPicker.AreaBackground />', ' <ColorPicker.AreaBackground />',
' <ColorPicker.AreaThumb />', ' <ColorPicker.AreaThumb />',
@ -168,6 +170,7 @@
' <ColorPicker.SwatchIndicator />', ' <ColorPicker.SwatchIndicator />',
' </ColorPicker.SwatchTrigger>{/each}', ' </ColorPicker.SwatchTrigger>{/each}',
' </ColorPicker.SwatchGroup>', ' </ColorPicker.SwatchGroup>',
' </PickerShell.Body>',
showFooter && (showClear || showCancel || showClose) && ' <PickerShell.Footer>', showFooter && (showClear || showCancel || showClose) && ' <PickerShell.Footer>',
showFooter && showClear && ' <PickerShell.Clear />', showFooter && showClear && ' <PickerShell.Clear />',
showFooter && showCancel && ' <PickerShell.Cancel />', showFooter && showCancel && ' <PickerShell.Cancel />',
@ -184,6 +187,7 @@
[ [
"<script lang='ts'>", "<script lang='ts'>",
" import { ColorPicker } from '$uix/eidos/components/color-picker';", " import { ColorPicker } from '$uix/eidos/components/color-picker';",
" import { PickerShell } from '$uix/eidos/components/picker-shell';",
" import { colorValueFromRgb, type ColorValue } from '$libs/color';", " import { colorValueFromRgb, type ColorValue } from '$libs/color';",
'', '',
' let value = $state<ColorValue | undefined>(colorValueFromRgb({ r: 30, g: 144, b: 255, a: 1 }));', ' let value = $state<ColorValue | undefined>(colorValueFromRgb({ r: 30, g: 144, b: 255, a: 1 }));',
@ -207,6 +211,7 @@
' </ColorPicker.Control>', ' </ColorPicker.Control>',
' <ColorPicker.Portal>', ' <ColorPicker.Portal>',
' <ColorPicker.Content>', ' <ColorPicker.Content>',
' <PickerShell.Body>',
' <ColorPicker.Area />', ' <ColorPicker.Area />',
' <ColorPicker.ChannelSlider channel="hue" />', ' <ColorPicker.ChannelSlider channel="hue" />',
enableAlpha && ' <ColorPicker.ChannelSlider channel="alpha" />', enableAlpha && ' <ColorPicker.ChannelSlider channel="alpha" />',
@ -217,6 +222,7 @@
' <ColorPicker.SwatchGroup>', ' <ColorPicker.SwatchGroup>',
' {#each presets as c}<ColorPicker.SwatchTrigger color={c} />{/each}', ' {#each presets as c}<ColorPicker.SwatchTrigger color={c} />{/each}',
' </ColorPicker.SwatchGroup>', ' </ColorPicker.SwatchGroup>',
' </PickerShell.Body>',
showFooter && (showClear || showCancel || showClose) && ' <ColorPicker.Footer>', showFooter && (showClear || showCancel || showClose) && ' <ColorPicker.Footer>',
showFooter && showClear && ' <ColorPicker.Clear />', showFooter && showClear && ' <ColorPicker.Clear />',
showFooter && showCancel && ' <ColorPicker.Cancel />', showFooter && showCancel && ' <ColorPicker.Cancel />',
@ -279,6 +285,7 @@
<ColorPicker.Portal> <ColorPicker.Portal>
<ColorPicker.Content> <ColorPicker.Content>
<PickerShell.Body>
<ColorPicker.Area /> <ColorPicker.Area />
<!-- Channel sliders with explicit row labels so users <!-- Channel sliders with explicit row labels so users
@ -318,6 +325,7 @@
</ColorPicker.SwatchTrigger> </ColorPicker.SwatchTrigger>
{/each} {/each}
</ColorPicker.SwatchGroup> </ColorPicker.SwatchGroup>
</PickerShell.Body>
{#if showFooter && (showClear || showCancel || showClose)} {#if showFooter && (showClear || showCancel || showClose)}
<ColorPicker.Footer> <ColorPicker.Footer>

@ -8,6 +8,7 @@
type DatePickerSize, type DatePickerSize,
type DatePickerVariant type DatePickerVariant
} from '$uix/eidos/components/date-picker'; } from '$uix/eidos/components/date-picker';
import { PickerShell } from '$uix/eidos/components/picker-shell';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { datePickerMorfo } from '@/uix/morfo/components/date-picker'; import { datePickerMorfo } from '@/uix/morfo/components/date-picker';
import { calendarMorfo } from '@/uix/morfo/components/calendar'; import { calendarMorfo } from '@/uix/morfo/components/calendar';
@ -65,6 +66,10 @@
let showClear = $state(true); let showClear = $state(true);
let showCancel = $state(true); let showCancel = $state(true);
let showClose = $state(true); let showClose = $state(true);
// PickerShell.Header toggle — demonstrates the optional top zone of
// the canonical 4-part picker layout. The Header sits above Body and
// renders with its own bottom border, mirroring the Footer.
let showShellHeader = $state(false);
let disableDaysOutsideMonth = $state(true); let disableDaysOutsideMonth = $state(true);
let fixedWeeks = $state(false); let fixedWeeks = $state(false);
@ -301,6 +306,8 @@
' </DatePicker.Input>', ' </DatePicker.Input>',
' <DatePicker.Portal>', ' <DatePicker.Portal>',
' <DatePicker.Content align="end" sideOffset={10} collisionPadding={12}>', ' <DatePicker.Content align="end" sideOffset={10} collisionPadding={12}>',
showShellHeader && ' <PickerShell.Header>Pick a date</PickerShell.Header>',
' <PickerShell.Body>',
' <DatePicker.Calendar>', ' <DatePicker.Calendar>',
' {#snippet children({ months, weekdays })}', ' {#snippet children({ months, weekdays })}',
' <DatePicker.Header>...</DatePicker.Header>', ' <DatePicker.Header>...</DatePicker.Header>',
@ -312,6 +319,12 @@
' {/each}', ' {/each}',
' {/snippet}', ' {/snippet}',
' </DatePicker.Calendar>', ' </DatePicker.Calendar>',
' </PickerShell.Body>',
showFooter && (showClear || showCancel || showClose) && ' <DatePicker.Footer>',
showFooter && showClear && ' <DatePicker.Clear />',
showFooter && showCancel && ' <DatePicker.Cancel />',
showFooter && showClose && ' <DatePicker.Close />',
showFooter && (showClear || showCancel || showClose) && ' </DatePicker.Footer>',
' </DatePicker.Content>', ' </DatePicker.Content>',
' </DatePicker.Portal>', ' </DatePicker.Portal>',
'</DatePicker.Provider>' '</DatePicker.Provider>'
@ -324,6 +337,7 @@
[ [
"<script lang='ts'>", "<script lang='ts'>",
" import { DatePicker } from '$uix/eidos/components/date-picker';", " import { DatePicker } from '$uix/eidos/components/date-picker';",
" import { PickerShell } from '$uix/eidos/components/picker-shell';",
" import { CalendarDate, type DateValue } from '$libs/days';", " import { CalendarDate, type DateValue } from '$libs/days';",
'', '',
` const minValue = ${dateCtor(minValue)};`, ` const minValue = ${dateCtor(minValue)};`,
@ -373,6 +387,8 @@
' </DatePicker.Input>', ' </DatePicker.Input>',
' <DatePicker.Portal>', ' <DatePicker.Portal>',
' <DatePicker.Content align="end" sideOffset={10} collisionPadding={12}>', ' <DatePicker.Content align="end" sideOffset={10} collisionPadding={12}>',
showShellHeader && ' <PickerShell.Header>Pick a date</PickerShell.Header>',
' <PickerShell.Body>',
' <DatePicker.Calendar>', ' <DatePicker.Calendar>',
' {#snippet children({ months, weekdays })}', ' {#snippet children({ months, weekdays })}',
' <DatePicker.Header>...</DatePicker.Header>', ' <DatePicker.Header>...</DatePicker.Header>',
@ -384,6 +400,12 @@
' {/each}', ' {/each}',
' {/snippet}', ' {/snippet}',
' </DatePicker.Calendar>', ' </DatePicker.Calendar>',
' </PickerShell.Body>',
showFooter && (showClear || showCancel || showClose) && ' <DatePicker.Footer>',
showFooter && showClear && ' <DatePicker.Clear />',
showFooter && showCancel && ' <DatePicker.Cancel />',
showFooter && showClose && ' <DatePicker.Close />',
showFooter && (showClear || showCancel || showClose) && ' </DatePicker.Footer>',
' </DatePicker.Content>', ' </DatePicker.Content>',
' </DatePicker.Portal>', ' </DatePicker.Portal>',
'</DatePicker>' '</DatePicker>'
@ -467,6 +489,10 @@
</div> </div>
<DatePicker.Portal> <DatePicker.Portal>
<DatePicker.Content align="end" sideOffset={10} collisionPadding={12}> <DatePicker.Content align="end" sideOffset={10} collisionPadding={12}>
{#if showShellHeader}
<PickerShell.Header>Pick a date</PickerShell.Header>
{/if}
<PickerShell.Body>
{#if kind === 'year'} {#if kind === 'year'}
<DatePicker.YearView /> <DatePicker.YearView />
{:else if kind === 'month'} {:else if kind === 'month'}
@ -478,6 +504,7 @@
{/snippet} {/snippet}
</DatePicker.Calendar> </DatePicker.Calendar>
{/if} {/if}
</PickerShell.Body>
{@render pickerFooter()} {@render pickerFooter()}
</DatePicker.Content> </DatePicker.Content>
</DatePicker.Portal> </DatePicker.Portal>
@ -671,6 +698,18 @@
></span ></span
> >
</label> </label>
<label data-uix-control>
<span data-uix-control-label
>shell header <span data-uix-control-hint
>optional &lt;PickerShell.Header&gt; zone</span
></span
>
<span data-uix-switch
><input type="checkbox" bind:checked={showShellHeader} /><span
data-uix-switch-label>Header</span
></span
>
</label>
<label data-uix-control> <label data-uix-control>
<span data-uix-control-label>calendar behavior</span> <span data-uix-control-label>calendar behavior</span>
<span data-uix-switch><input type="checkbox" bind:checked={closeOnDateSelect} /><span data-uix-switch-label>close on select</span></span> <span data-uix-switch><input type="checkbox" bind:checked={closeOnDateSelect} /><span data-uix-switch-label>close on select</span></span>

@ -8,6 +8,7 @@
type DateRangePickerSize, type DateRangePickerSize,
type DateRangePickerVariant type DateRangePickerVariant
} from '$uix/eidos/components/date-range-picker'; } from '$uix/eidos/components/date-range-picker';
import { PickerShell } from '$uix/eidos/components/picker-shell';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { dateRangePickerMorfo } from '@/uix/morfo/components/date-range-picker'; import { dateRangePickerMorfo } from '@/uix/morfo/components/date-range-picker';
import { dateRangeFieldMorfo } from '@/uix/morfo/components/date-range-field'; import { dateRangeFieldMorfo } from '@/uix/morfo/components/date-range-field';
@ -387,7 +388,14 @@
' </DateRangePicker.Input>', ' </DateRangePicker.Input>',
' <DateRangePicker.Portal>', ' <DateRangePicker.Portal>',
' <DateRangePicker.Content align="end" sideOffset={10} collisionPadding={12}>', ' <DateRangePicker.Content align="end" sideOffset={10} collisionPadding={12}>',
' <PickerShell.Body>',
' <DateRangePicker.Calendar>...</DateRangePicker.Calendar>', ' <DateRangePicker.Calendar>...</DateRangePicker.Calendar>',
' </PickerShell.Body>',
' <DateRangePicker.Footer>',
' <DateRangePicker.Clear />',
' <DateRangePicker.Cancel />',
' <DateRangePicker.Close />',
' </DateRangePicker.Footer>',
' </DateRangePicker.Content>', ' </DateRangePicker.Content>',
' </DateRangePicker.Portal>', ' </DateRangePicker.Portal>',
'</DateRangePicker.Provider>' '</DateRangePicker.Provider>'
@ -400,6 +408,7 @@
[ [
"<script lang='ts'>", "<script lang='ts'>",
" import { DateRangePicker } from '$uix/eidos/components/date-range-picker';", " import { DateRangePicker } from '$uix/eidos/components/date-range-picker';",
" import { PickerShell } from '$uix/eidos/components/picker-shell';",
" import { CalendarDate, type DateRange, type DateValue } from '$libs/days';", " import { CalendarDate, type DateRange, type DateValue } from '$libs/days';",
'', '',
` const minValue = ${dateCtor(minValue)};`, ` const minValue = ${dateCtor(minValue)};`,
@ -449,7 +458,14 @@
' </div>', ' </div>',
' <DateRangePicker.Portal>', ' <DateRangePicker.Portal>',
' <DateRangePicker.Content align="end" sideOffset={10} collisionPadding={12}>', ' <DateRangePicker.Content align="end" sideOffset={10} collisionPadding={12}>',
' <PickerShell.Body>',
' <DateRangePicker.Calendar>...</DateRangePicker.Calendar>', ' <DateRangePicker.Calendar>...</DateRangePicker.Calendar>',
' </PickerShell.Body>',
' <DateRangePicker.Footer>',
' <DateRangePicker.Clear />',
' <DateRangePicker.Cancel />',
' <DateRangePicker.Close />',
' </DateRangePicker.Footer>',
' </DateRangePicker.Content>', ' </DateRangePicker.Content>',
' </DateRangePicker.Portal>', ' </DateRangePicker.Portal>',
'</DateRangePicker>' '</DateRangePicker>'
@ -539,6 +555,7 @@
</div> </div>
<DateRangePicker.Portal> <DateRangePicker.Portal>
<DateRangePicker.Content align="end" sideOffset={10} collisionPadding={12}> <DateRangePicker.Content align="end" sideOffset={10} collisionPadding={12}>
<PickerShell.Body>
{#if kind === 'year'} {#if kind === 'year'}
<DateRangePicker.YearView /> <DateRangePicker.YearView />
{:else if kind === 'month'} {:else if kind === 'month'}
@ -550,6 +567,7 @@
{/snippet} {/snippet}
</DateRangePicker.Calendar> </DateRangePicker.Calendar>
{/if} {/if}
</PickerShell.Body>
{@render pickerFooter()} {@render pickerFooter()}
</DateRangePicker.Content> </DateRangePicker.Content>
</DateRangePicker.Portal> </DateRangePicker.Portal>

@ -6,6 +6,7 @@
type TimePickerVariant, type TimePickerVariant,
type InputSnippetProps type InputSnippetProps
} from '$uix/eidos/components/time-picker'; } from '$uix/eidos/components/time-picker';
import { PickerShell } from '$uix/eidos/components/picker-shell';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { timePickerMorfo } from '@/uix/morfo/components/time-picker'; import { timePickerMorfo } from '@/uix/morfo/components/time-picker';
import { timeFieldMorfo } from '@/uix/morfo/components/time-field'; import { timeFieldMorfo } from '@/uix/morfo/components/time-field';
@ -156,6 +157,7 @@
[ [
"<script lang='ts'>", "<script lang='ts'>",
" import { TimePicker } from '$uix/eidos/components/time-picker';", " import { TimePicker } from '$uix/eidos/components/time-picker';",
" import { PickerShell } from '$uix/eidos/components/picker-shell';",
" import { Time, type TimeValue } from '$libs/days';", " import { Time, type TimeValue } from '$libs/days';",
'', '',
' let value = $state<TimeValue | undefined>(new Time(9, 30, 0));', ' let value = $state<TimeValue | undefined>(new Time(9, 30, 0));',
@ -186,17 +188,19 @@
' </TimePicker.Input>', ' </TimePicker.Input>',
' <TimePicker.Portal>', ' <TimePicker.Portal>',
' <TimePicker.Content>', ' <TimePicker.Content>',
' <PickerShell.Body>',
' <TimePicker.Clock>', ' <TimePicker.Clock>',
' <TimePicker.HourSlider />', ' <TimePicker.HourSlider />',
' <TimePicker.MinuteSlider />', ' <TimePicker.MinuteSlider />',
granularity === 'second' && ' <TimePicker.SecondSlider />', granularity === 'second' && ' <TimePicker.SecondSlider />',
hourCycle === 12 && ' <TimePicker.DayPeriodToggle />', hourCycle === 12 && ' <TimePicker.DayPeriodToggle />',
' </TimePicker.Clock>',
' </PickerShell.Body>',
showFooter && (showClear || showCancel || showClose) && ' <TimePicker.Footer>', showFooter && (showClear || showCancel || showClose) && ' <TimePicker.Footer>',
showFooter && showClear && ' <TimePicker.Clear />', showFooter && showClear && ' <TimePicker.Clear />',
showFooter && showCancel && ' <TimePicker.Cancel />', showFooter && showCancel && ' <TimePicker.Cancel />',
showFooter && showClose && ' <TimePicker.Close />', showFooter && showClose && ' <TimePicker.Close />',
showFooter && (showClear || showCancel || showClose) && ' </TimePicker.Footer>', showFooter && (showClear || showCancel || showClose) && ' </TimePicker.Footer>',
' </TimePicker.Clock>',
' </TimePicker.Content>', ' </TimePicker.Content>',
' </TimePicker.Portal>', ' </TimePicker.Portal>',
'</TimePicker>' '</TimePicker>'
@ -259,6 +263,7 @@
<TimePicker.Portal> <TimePicker.Portal>
<TimePicker.Content> <TimePicker.Content>
<PickerShell.Body>
<TimePicker.Clock> <TimePicker.Clock>
<TimePicker.HourSlider /> <TimePicker.HourSlider />
<TimePicker.MinuteSlider /> <TimePicker.MinuteSlider />
@ -268,6 +273,8 @@
{#if hourCycle === 12} {#if hourCycle === 12}
<TimePicker.DayPeriodToggle /> <TimePicker.DayPeriodToggle />
{/if} {/if}
</TimePicker.Clock>
</PickerShell.Body>
{#if showFooter && (showClear || showCancel || showClose)} {#if showFooter && (showClear || showCancel || showClose)}
<TimePicker.Footer> <TimePicker.Footer>
@ -276,7 +283,6 @@
{#if showClose}<TimePicker.Close />{/if} {#if showClose}<TimePicker.Close />{/if}
</TimePicker.Footer> </TimePicker.Footer>
{/if} {/if}
</TimePicker.Clock>
</TimePicker.Content> </TimePicker.Content>
</TimePicker.Portal> </TimePicker.Portal>
</TimePicker> </TimePicker>

@ -6,6 +6,7 @@
type TimeRangePickerVariant, type TimeRangePickerVariant,
type InputSnippetProps type InputSnippetProps
} from '$uix/eidos/components/time-range-picker'; } from '$uix/eidos/components/time-range-picker';
import { PickerShell } from '$uix/eidos/components/picker-shell';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { timeRangePickerMorfo } from '@/uix/morfo/components/time-range-picker'; import { timeRangePickerMorfo } from '@/uix/morfo/components/time-range-picker';
import { timeRangeFieldMorfo } from '@/uix/morfo/components/time-range-field'; import { timeRangeFieldMorfo } from '@/uix/morfo/components/time-range-field';
@ -170,6 +171,7 @@
[ [
"<script lang='ts'>", "<script lang='ts'>",
" import { TimeRangePicker } from '$uix/eidos/components/time-range-picker';", " import { TimeRangePicker } from '$uix/eidos/components/time-range-picker';",
" import { PickerShell } from '$uix/eidos/components/picker-shell';",
" import { Time, type TimeRange } from '$libs/days';", " import { Time, type TimeRange } from '$libs/days';",
'', '',
' let value = $state<TimeRange | undefined>({', ' let value = $state<TimeRange | undefined>({',
@ -211,6 +213,7 @@
' </TimeRangePicker.Input>', ' </TimeRangePicker.Input>',
' <TimeRangePicker.Portal>', ' <TimeRangePicker.Portal>',
' <TimeRangePicker.Content>', ' <TimeRangePicker.Content>',
' <PickerShell.Body>',
' <TimeRangePicker.Clock>', ' <TimeRangePicker.Clock>',
' <div class="time-range-picker-endpoint">', ' <div class="time-range-picker-endpoint">',
' <span class="time-range-picker-endpoint-label">Start</span>', ' <span class="time-range-picker-endpoint-label">Start</span>',
@ -226,16 +229,13 @@
granularity === 'second' && ' <TimeRangePicker.SecondSlider type="end" />', granularity === 'second' && ' <TimeRangePicker.SecondSlider type="end" />',
hourCycle === 12 && ' <TimeRangePicker.DayPeriodToggle type="end" />', hourCycle === 12 && ' <TimeRangePicker.DayPeriodToggle type="end" />',
' </div>', ' </div>',
showFooter && ' </TimeRangePicker.Clock>',
(showClear || showCancel || showClose) && ' </PickerShell.Body>',
' <TimeRangePicker.Footer>', showFooter && (showClear || showCancel || showClose) && ' <TimeRangePicker.Footer>',
showFooter && showClear && ' <TimeRangePicker.Clear />', showFooter && showClear && ' <TimeRangePicker.Clear />',
showFooter && showCancel && ' <TimeRangePicker.Cancel />', showFooter && showCancel && ' <TimeRangePicker.Cancel />',
showFooter && showClose && ' <TimeRangePicker.Close />', showFooter && showClose && ' <TimeRangePicker.Close />',
showFooter && showFooter && (showClear || showCancel || showClose) && ' </TimeRangePicker.Footer>',
(showClear || showCancel || showClose) &&
' </TimeRangePicker.Footer>',
' </TimeRangePicker.Clock>',
' </TimeRangePicker.Content>', ' </TimeRangePicker.Content>',
' </TimeRangePicker.Portal>', ' </TimeRangePicker.Portal>',
'</TimeRangePicker>' '</TimeRangePicker>'
@ -308,6 +308,7 @@
<TimeRangePicker.Portal> <TimeRangePicker.Portal>
<TimeRangePicker.Content> <TimeRangePicker.Content>
<PickerShell.Body>
<TimeRangePicker.Clock> <TimeRangePicker.Clock>
<div class="time-range-picker-endpoint"> <div class="time-range-picker-endpoint">
<span class="time-range-picker-endpoint-label">Start</span> <span class="time-range-picker-endpoint-label">Start</span>
@ -331,6 +332,8 @@
<TimeRangePicker.DayPeriodToggle type="end" /> <TimeRangePicker.DayPeriodToggle type="end" />
{/if} {/if}
</div> </div>
</TimeRangePicker.Clock>
</PickerShell.Body>
{#if showFooter && (showClear || showCancel || showClose)} {#if showFooter && (showClear || showCancel || showClose)}
<TimeRangePicker.Footer> <TimeRangePicker.Footer>
@ -339,7 +342,6 @@
{#if showClose}<TimeRangePicker.Close />{/if} {#if showClose}<TimeRangePicker.Close />{/if}
</TimeRangePicker.Footer> </TimeRangePicker.Footer>
{/if} {/if}
</TimeRangePicker.Clock>
</TimeRangePicker.Content> </TimeRangePicker.Content>
</TimeRangePicker.Portal> </TimeRangePicker.Portal>
</TimeRangePicker> </TimeRangePicker>

Loading…
Cancel
Save

Powered by TurnKey Linux.