Replicates the date-range-picker pattern on the single date-picker:
soma:
- DatePickerMode type ('inline' | 'modal') exported.
- Provider gains mode/clearButton/cancelButton/closeButton opts plus
commit() / cancel() / clear() action helpers; cancel() reverts the
value snapshot taken via watch() on the OPEN edge of opts.open.
- handleDateSelect now early-returns when mode === 'modal' so modal
pickers don't auto-close on selection.
- exports.ts surfaces DatePickerProvider + DatePickerMode for demo /
footer wiring (mirrors date-range-picker's barrel).
- Test fixture extended with the new opts (closeOnDateSelect stays
true by default; mode='inline', all buttons false).
morfo:
- scope=['soma','sema'], apg=dialog-modal.
- 6 events: open / close-commit / close-cancel / close-dismiss /
close-dismiss-outside / commit-clear. prewrite stamps data-last-action
with the causal exit reason so Sema can tint the exit animation.
- Calendar part declares the state machine (open/closed,
data-last-action, data-starting-style, data-ending-style) and the
modal-keyboard surface (Escape / Tab / Shift+Tab).
demo:
- New picker-actions.svelte mirrors the range demo: pulls provider via
context, renders Clear / Cancel / Close conditionally, forces Close in
modal mode. CSS is local to the file (uses --color-primary-* +
--color-surface-overlay tokens, no raw colors).
- +page.svelte adds mode radio chip group + 3 footer-button switches and
passes mode/clearButton/cancelButton/closeButton through to the Eidos
wrapper (which spreads to soma).
The Eidos wrapper needs no change — it already spreads everything via
...rest, so the new soma opts reach Soma without further wiring.
Verification: 0 type errors, 3/3 soma tests pass, morfo:check PASS,
component:audit 67/67 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
parent
6144837174
commit
9bd9db408d
@ -0,0 +1,111 @@
|
||||
<script lang="ts">
|
||||
import { DatePickerProvider } from '$soma/components/date-picker';
|
||||
|
||||
// Footer for the DatePicker calendar. Lives inside the provider tree
|
||||
// (rendered from the Calendar snippet) so it can pull options + helpers
|
||||
// straight from context.
|
||||
//
|
||||
// Buttons are configured via props on the picker:
|
||||
// - `clearButton` — Clear (reset value, keep open)
|
||||
// - `cancelButton` — Cancel (revert to value-at-open, close)
|
||||
// - `closeButton` — Close / Save (commit current value, close)
|
||||
//
|
||||
// In `mode='modal'` Close is forced on so the user always has a way out:
|
||||
// modal popovers ignore outside-click and Escape.
|
||||
//
|
||||
// If no buttons are enabled (and the picker is not modal), this footer
|
||||
// renders nothing.
|
||||
const provider = DatePickerProvider.require();
|
||||
const mode = $derived(provider.opts.mode.current);
|
||||
const showClear = $derived(provider.opts.clearButton.current);
|
||||
const showCancel = $derived(provider.opts.cancelButton.current);
|
||||
const showClose = $derived(provider.opts.closeButton.current || mode === 'modal');
|
||||
const visible = $derived(showClear || showCancel || showClose);
|
||||
</script>
|
||||
|
||||
{#if visible}
|
||||
<div class="picker-footer" data-date-picker-footer data-mode={mode}>
|
||||
{#if showClear}
|
||||
<button
|
||||
type="button"
|
||||
class="picker-footer-button"
|
||||
data-tone="ghost"
|
||||
onclick={() => provider.clear()}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
{/if}
|
||||
<div class="picker-footer-spacer"></div>
|
||||
{#if showCancel}
|
||||
<button
|
||||
type="button"
|
||||
class="picker-footer-button"
|
||||
data-tone="ghost"
|
||||
onclick={() => provider.cancel()}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
{/if}
|
||||
{#if showClose}
|
||||
<button
|
||||
type="button"
|
||||
class="picker-footer-button"
|
||||
data-tone="solid"
|
||||
onclick={() => provider.commit()}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.picker-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uix-space-2, 0.5rem);
|
||||
padding-block-start: var(--uix-space-3, 0.75rem);
|
||||
margin-block-start: var(--uix-space-3, 0.75rem);
|
||||
border-block-start: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.picker-footer-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.picker-footer-button {
|
||||
inline-size: max-content;
|
||||
min-block-size: var(--control-height-sm);
|
||||
padding-inline: var(--uix-space-3, 0.75rem);
|
||||
border-radius: var(--radius-md);
|
||||
border: var(--border-width) solid transparent;
|
||||
background: transparent;
|
||||
color: var(--color-content-primary);
|
||||
font: inherit;
|
||||
font-size: var(--uix-text-sm);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background var(--duration-fast) var(--ease-default),
|
||||
border-color var(--duration-fast) var(--ease-default),
|
||||
color var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
.picker-footer-button[data-tone='ghost']:hover {
|
||||
background: var(--color-surface-overlay);
|
||||
}
|
||||
|
||||
.picker-footer-button[data-tone='solid'] {
|
||||
border-color: var(--color-primary-border);
|
||||
background: var(--color-primary-solid);
|
||||
color: var(--color-content-on-solid);
|
||||
}
|
||||
|
||||
.picker-footer-button[data-tone='solid']:hover {
|
||||
background: var(--color-primary-solid-hover);
|
||||
}
|
||||
|
||||
.picker-footer-button:focus-visible {
|
||||
outline: var(--focus-ring-width) solid var(--color-primary-border);
|
||||
outline-offset: var(--focus-ring-offset);
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in new issue