parent
09e2618787
commit
fb5dd0919c
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Ellipsis as SomaBreadcrumbEllipsis } from '$soma/components/breadcrumb';
|
||||
import * as Breadcrumb from '$soma/components/breadcrumb';
|
||||
import type { BreadcrumbEllipsisProps } from './types';
|
||||
|
||||
let { children, ...rest }: BreadcrumbEllipsisProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaBreadcrumbEllipsis {...rest}>
|
||||
<Breadcrumb.Ellipsis {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaBreadcrumbEllipsis>
|
||||
</Breadcrumb.Ellipsis>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Item as SomaBreadcrumbItem } from '$soma/components/breadcrumb';
|
||||
import * as Breadcrumb from '$soma/components/breadcrumb';
|
||||
import type { BreadcrumbItemProps } from './types';
|
||||
|
||||
let { children, ...rest }: BreadcrumbItemProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaBreadcrumbItem {...rest}>
|
||||
<Breadcrumb.Item {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaBreadcrumbItem>
|
||||
</Breadcrumb.Item>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Link as SomaBreadcrumbLink } from '$soma/components/breadcrumb';
|
||||
import * as Breadcrumb from '$soma/components/breadcrumb';
|
||||
import type { BreadcrumbLinkProps } from './types';
|
||||
|
||||
let { children, ...rest }: BreadcrumbLinkProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaBreadcrumbLink {...rest}>
|
||||
<Breadcrumb.Link {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaBreadcrumbLink>
|
||||
</Breadcrumb.Link>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { List as SomaBreadcrumbList } from '$soma/components/breadcrumb';
|
||||
import * as Breadcrumb from '$soma/components/breadcrumb';
|
||||
import type { BreadcrumbListProps } from './types';
|
||||
|
||||
let { children, ...rest }: BreadcrumbListProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaBreadcrumbList {...rest}>
|
||||
<Breadcrumb.List {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaBreadcrumbList>
|
||||
</Breadcrumb.List>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Separator as SomaBreadcrumbSeparator } from '$soma/components/breadcrumb';
|
||||
import * as Breadcrumb from '$soma/components/breadcrumb';
|
||||
import type { BreadcrumbSeparatorProps } from './types';
|
||||
|
||||
let { children, ...rest }: BreadcrumbSeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaBreadcrumbSeparator {...rest}>
|
||||
<Breadcrumb.Separator {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaBreadcrumbSeparator>
|
||||
</Breadcrumb.Separator>
|
||||
|
||||
@ -0,0 +1,136 @@
|
||||
# Combobox
|
||||
|
||||
Eidos Combobox wraps Soma Combobox and keeps the component compound:
|
||||
`<Combobox>` + `Control`, `Input`, `Trigger`, `Indicator`, `Portal`,
|
||||
`Content`, `Viewport`, `Item`, `ItemIndicator`, `Group`, `GroupHeading`,
|
||||
`Separator` and `Arrow`.
|
||||
|
||||
## Comparison
|
||||
|
||||
| Feature | Air | Radix | Bits | Ark | shadcn-svelte | React Aria | Soma | Eidos | Decision |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| Editable input + popup listbox | ✅ | ❌ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | Covered with `Input` + `Content`; Radix has no dedicated Combobox primitive. |
|
||||
| Compound anatomy | ✅ | ❌ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | Eidos exposes one namespace and attached parts; the root is `<Combobox>` itself. |
|
||||
| Input + trigger grouped visually | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | `Combobox.Control` is Eidos-only layout. |
|
||||
| Portal + floating content + arrow | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Soma owns Floating; Eidos consumes `data-side` and floating CSS vars. |
|
||||
| Groups, headings and separators | ✅ | ❌ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | Covered. |
|
||||
| Selected item indicator | ⚠️ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | `ItemIndicator` is Eidos-only; item state comes from Soma/Morfo. |
|
||||
| Single select | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Covered. |
|
||||
| Multiple select | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | Covered by Soma `type="multiple"`; Eidos only styles selected items. |
|
||||
| Typeahead / filtering input | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Soma owns input value and filtering surface. |
|
||||
| Hidden form value | ✅ | ❌ | ✅ | ✅ | ⚠️ | ✅ | ✅ | ✅ | Soma renders hidden inputs when `name` is passed. |
|
||||
| Clear trigger | ❌ | ❌ | ⚠️ | ✅ | ❌ | ✅ | ❌ | ❌ | Defer to Soma; clearing changes value/input state. |
|
||||
| Scroll up/down affordance parts | ✅ | ❌ | ✅ | ⚠️ | ❌ | ❌ | ❌ | ❌ | Defer to Soma; requires viewport scroll behavior. |
|
||||
| Static/non-floating content | ✅ | ❌ | ⚠️ | ⚠️ | ❌ | ❌ | ❌ | ❌ | Defer to Soma if inline listbox topology is needed. |
|
||||
| Menu trigger behavior | ❌ | ❌ | ⚠️ | ✅ | ❌ | ✅ | ❌ | ❌ | Defer to Soma; affects when open state changes. |
|
||||
| Inline autocomplete / auto highlight | ❌ | ❌ | ⚠️ | ✅ | ❌ | ✅ | ❌ | ❌ | Defer to Soma; input behavior, not visual styling. |
|
||||
| Custom value / freeform commit | ❌ | ❌ | ⚠️ | ⚠️ | ❌ | ✅ | ❌ | ❌ | Defer to Soma/Form; needs value semantics. |
|
||||
| Validation state | ✅ | ❌ | ⚠️ | ✅ | ⚠️ | ✅ | ❌ | ❌ | Defer to Field/Soma; Eidos must not invent invalid state. |
|
||||
| Async/virtualized collections | ❌ | ❌ | ⚠️ | ✅ | ❌ | ✅ | ❌ | ❌ | Belongs in collection/virtualization, not visual wrapper. |
|
||||
|
||||
References:
|
||||
- Bits UI Combobox: https://bits-ui.com/docs/components/combobox
|
||||
- Ark UI Combobox: https://ark-ui.com/docs/components/combobox
|
||||
- shadcn-svelte Combobox: https://www.shadcn-svelte.com/docs/components/combobox
|
||||
- React Aria ComboBox: https://react-spectrum.adobe.com/react-aria/ComboBox.html
|
||||
- Radix Primitives overview: https://www.radix-ui.com/primitives/docs/overview/introduction
|
||||
|
||||
## Layer split
|
||||
|
||||
| Layer | Owns |
|
||||
| --- | --- |
|
||||
| Morfo | Parts, ARIA/data contract, `commit-select` and `commit-unselect` events. |
|
||||
| Soma | Input text, open state, single/multiple values, hidden form inputs, groups, floating, dismissal and keyboard navigation. |
|
||||
| Sema | Subtle form commit sounds + tap haptic for select/unselect. Typing, filtering, open/close and highlight are intentionally silent. |
|
||||
| Eidos | Control layout, size, variant, intent color, trigger icon, selected item indicator, panel recipe and animation. |
|
||||
|
||||
## Morfo/Sema
|
||||
|
||||
| Event | Family | Verb | Intent | Target | Sequence | Reason |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| `commit-select` | `commit` | `select` | `affirm` | `item` | `post` | User confirms an option. The concrete item is the perceived target. |
|
||||
| `commit-unselect` | `commit` | `remove` | `neutral` | `item` | `post` | Multiple deselect or single clear via `allowDeselect`. |
|
||||
|
||||
Opening, closing, typing, filtering and highlighting are navigation mechanics,
|
||||
not committed choices. They do not emit Sema events.
|
||||
|
||||
## Out of scope (v2 roadmap)
|
||||
|
||||
- `ClearTrigger`: Ark and React Aria expose clear behavior. Deferred to Soma
|
||||
because it mutates `value` and `inputValue`. Estimated cost: 80-140 lines
|
||||
across Morfo/Soma/Eidos.
|
||||
- `ScrollUpButton` / `ScrollDownButton`: Air and Bits expose them. Deferred
|
||||
to Soma because the part needs scroll behavior and viewport awareness.
|
||||
Estimated cost: 120-180 lines.
|
||||
- `ContentStatic`: Air exposes a non-floating content variant. Deferred to
|
||||
Soma because it changes topology and dismissal behavior. Estimated cost:
|
||||
80-140 lines.
|
||||
- `menuTrigger`, `autoHighlight`, `inlineAutocomplete`: Ark and React Aria
|
||||
cover these input behaviors. Deferred to Soma because they affect open,
|
||||
highlighted and input value state. Estimated cost: 180-260 lines.
|
||||
- Custom/freeform value: React Aria supports custom values. Deferred to
|
||||
Soma/Form because it changes value semantics and submitted form value.
|
||||
Estimated cost: 140-220 lines.
|
||||
- Validation state: Ark and React Aria expose validation. Deferred to
|
||||
Field/Soma so Combobox consumes real field state instead of visual-only
|
||||
invalid selectors. Estimated cost: 120-200 lines.
|
||||
- Async/virtualized collections: Ark and React Aria cover this class of
|
||||
behavior. Deferred to the collection/virtualization layer, not Eidos.
|
||||
|
||||
## Usage
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { Combobox } from '$uix/eidos/components/combobox'
|
||||
|
||||
let value = $state<string[]>([])
|
||||
let inputValue = $state('')
|
||||
</script>
|
||||
|
||||
<Combobox bind:value bind:inputValue name="framework" size="md" variant="surface" color="primary">
|
||||
<Combobox.Control>
|
||||
<Combobox.Input placeholder="Search framework..." />
|
||||
<Combobox.Trigger>
|
||||
<Combobox.Indicator />
|
||||
</Combobox.Trigger>
|
||||
</Combobox.Control>
|
||||
<Combobox.Portal>
|
||||
<Combobox.Content sideOffset={6}>
|
||||
<Combobox.Viewport>
|
||||
<Combobox.Group>
|
||||
<Combobox.GroupHeading>Frontend</Combobox.GroupHeading>
|
||||
<Combobox.Item value="svelte" label="Svelte">
|
||||
<span>Svelte</span>
|
||||
<Combobox.ItemIndicator />
|
||||
</Combobox.Item>
|
||||
<Combobox.Item value="react" label="React">
|
||||
<span>React</span>
|
||||
<Combobox.ItemIndicator />
|
||||
</Combobox.Item>
|
||||
<Combobox.Item value="vue" label="Vue">
|
||||
<span>Vue</span>
|
||||
<Combobox.ItemIndicator />
|
||||
</Combobox.Item>
|
||||
<Combobox.Item value="solid" label="Solid">
|
||||
<span>Solid</span>
|
||||
<Combobox.ItemIndicator />
|
||||
</Combobox.Item>
|
||||
</Combobox.Group>
|
||||
<Combobox.Separator />
|
||||
<Combobox.Group>
|
||||
<Combobox.GroupHeading>Runtime</Combobox.GroupHeading>
|
||||
<Combobox.Item value="node" label="Node">
|
||||
<span>Node</span>
|
||||
<Combobox.ItemIndicator />
|
||||
</Combobox.Item>
|
||||
<Combobox.Item value="deno" label="Deno">
|
||||
<span>Deno</span>
|
||||
<Combobox.ItemIndicator />
|
||||
</Combobox.Item>
|
||||
</Combobox.Group>
|
||||
</Combobox.Viewport>
|
||||
<Combobox.Arrow />
|
||||
</Combobox.Content>
|
||||
</Combobox.Portal>
|
||||
</Combobox>
|
||||
```
|
||||
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as Combobox from '$soma/components/combobox';
|
||||
import type { ComboboxArrowProps } from './types';
|
||||
|
||||
let { children, ...rest }: ComboboxArrowProps = $props();
|
||||
</script>
|
||||
|
||||
<Combobox.Arrow {...rest}>
|
||||
{@render children?.()}
|
||||
</Combobox.Arrow>
|
||||
@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
import * as Combobox from '$soma/components/combobox';
|
||||
import { composeInlineStyle } from '$uix/eidos/lib/style';
|
||||
import { getComboboxVisualContext } from './context.svelte';
|
||||
import type { ComboboxContentProps } from './types';
|
||||
|
||||
let {
|
||||
size,
|
||||
variant,
|
||||
color,
|
||||
matchAnchorWidth = true,
|
||||
style,
|
||||
children: bodyContent,
|
||||
...rest
|
||||
}: ComboboxContentProps = $props();
|
||||
|
||||
const eidos = ActiveEidos.require();
|
||||
const visual = getComboboxVisualContext();
|
||||
const resolvedSize = $derived(eidos.resolve(size ?? visual?.getSize(), 'md'));
|
||||
const resolvedVariant = $derived(variant ?? visual?.getVariant() ?? 'surface');
|
||||
const resolvedColor = $derived(color ?? visual?.getColor() ?? 'primary');
|
||||
const resolvedMatchAnchorWidth = $derived(eidos.resolve(matchAnchorWidth, true));
|
||||
const composedStyle = $derived(
|
||||
composeInlineStyle(
|
||||
resolvedMatchAnchorWidth
|
||||
? '--_combobox-match-anchor-width: var(--floating-anchor-width);'
|
||||
: undefined,
|
||||
style
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<Combobox.Content
|
||||
{...rest}
|
||||
style={composedStyle}
|
||||
data-size={resolvedSize}
|
||||
data-variant={resolvedVariant}
|
||||
data-color={resolvedColor}
|
||||
>
|
||||
{#snippet children(snippetProps: { open: boolean })}
|
||||
{@render bodyContent?.(snippetProps)}
|
||||
{/snippet}
|
||||
</Combobox.Content>
|
||||
@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
import { getComboboxVisualContext } from './context.svelte';
|
||||
import type { ComboboxControlProps } from './types';
|
||||
|
||||
let { size, variant, color, children, ...rest }: ComboboxControlProps = $props();
|
||||
|
||||
const eidos = ActiveEidos.require();
|
||||
const visual = getComboboxVisualContext();
|
||||
const resolvedSize = $derived(eidos.resolve(size ?? visual?.getSize(), 'md'));
|
||||
const resolvedVariant = $derived(variant ?? visual?.getVariant() ?? 'surface');
|
||||
const resolvedColor = $derived(color ?? visual?.getColor() ?? 'primary');
|
||||
</script>
|
||||
|
||||
<div
|
||||
{...rest}
|
||||
data-combobox-control=""
|
||||
data-size={resolvedSize}
|
||||
data-variant={resolvedVariant}
|
||||
data-color={resolvedColor}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as Combobox from '$soma/components/combobox';
|
||||
import type { ComboboxGroupHeadingProps } from './types';
|
||||
|
||||
let { children, ...rest }: ComboboxGroupHeadingProps = $props();
|
||||
</script>
|
||||
|
||||
<Combobox.GroupHeading {...rest}>
|
||||
{@render children?.()}
|
||||
</Combobox.GroupHeading>
|
||||
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as Combobox from '$soma/components/combobox';
|
||||
import type { ComboboxGroupProps } from './types';
|
||||
|
||||
let { children, ...rest }: ComboboxGroupProps = $props();
|
||||
</script>
|
||||
|
||||
<Combobox.Group {...rest}>
|
||||
{@render children?.()}
|
||||
</Combobox.Group>
|
||||
@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { SvgChevron } from '$uix/eidos/components/svg';
|
||||
import type { ComboboxIndicatorProps } from './types';
|
||||
|
||||
let { children, ...rest }: ComboboxIndicatorProps = $props();
|
||||
</script>
|
||||
|
||||
<span {...rest} data-combobox-indicator="" aria-hidden="true">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<SvgChevron />
|
||||
{/if}
|
||||
</span>
|
||||
@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
import * as Combobox from '$soma/components/combobox';
|
||||
import { getComboboxVisualContext } from './context.svelte';
|
||||
import type { ComboboxInputProps } from './types';
|
||||
|
||||
let { size, variant, color, ...rest }: ComboboxInputProps = $props();
|
||||
|
||||
const eidos = ActiveEidos.require();
|
||||
const visual = getComboboxVisualContext();
|
||||
const resolvedSize = $derived(eidos.resolve(size ?? visual?.getSize(), 'md'));
|
||||
const resolvedVariant = $derived(variant ?? visual?.getVariant() ?? 'surface');
|
||||
const resolvedColor = $derived(color ?? visual?.getColor() ?? 'primary');
|
||||
</script>
|
||||
|
||||
<Combobox.Input
|
||||
{...rest}
|
||||
data-size={resolvedSize}
|
||||
data-variant={resolvedVariant}
|
||||
data-color={resolvedColor}
|
||||
/>
|
||||
@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { SvgCheck } from '$uix/eidos/components/svg';
|
||||
import type { ComboboxItemIndicatorProps } from './types';
|
||||
|
||||
let { children, ...rest }: ComboboxItemIndicatorProps = $props();
|
||||
</script>
|
||||
|
||||
<span {...rest} data-combobox-item-indicator="" aria-hidden="true">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<SvgCheck />
|
||||
{/if}
|
||||
</span>
|
||||
@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import * as Combobox from '$soma/components/combobox';
|
||||
import type { ComboboxItemProps } from './types';
|
||||
|
||||
let { children, ...rest }: ComboboxItemProps = $props();
|
||||
</script>
|
||||
|
||||
<Combobox.Item {...rest}>
|
||||
{#snippet children(snippetProps)}
|
||||
{@render children?.(snippetProps)}
|
||||
{/snippet}
|
||||
</Combobox.Item>
|
||||
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as Combobox from '$soma/components/combobox';
|
||||
import type { ComboboxSeparatorProps } from './types';
|
||||
|
||||
let { children, ...rest }: ComboboxSeparatorProps = $props();
|
||||
</script>
|
||||
|
||||
<Combobox.Separator {...rest}>
|
||||
{@render children?.()}
|
||||
</Combobox.Separator>
|
||||
@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
import * as Combobox from '$soma/components/combobox';
|
||||
import { getComboboxVisualContext } from './context.svelte';
|
||||
import type { ComboboxTriggerProps } from './types';
|
||||
|
||||
let { size, variant, color, children, ...rest }: ComboboxTriggerProps = $props();
|
||||
|
||||
const eidos = ActiveEidos.require();
|
||||
const visual = getComboboxVisualContext();
|
||||
const resolvedSize = $derived(eidos.resolve(size ?? visual?.getSize(), 'md'));
|
||||
const resolvedVariant = $derived(variant ?? visual?.getVariant() ?? 'surface');
|
||||
const resolvedColor = $derived(color ?? visual?.getColor() ?? 'primary');
|
||||
</script>
|
||||
|
||||
<Combobox.Trigger
|
||||
{...rest}
|
||||
data-size={resolvedSize}
|
||||
data-variant={resolvedVariant}
|
||||
data-color={resolvedColor}
|
||||
>
|
||||
{@render children?.()}
|
||||
</Combobox.Trigger>
|
||||
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as Combobox from '$soma/components/combobox';
|
||||
import type { ComboboxViewportProps } from './types';
|
||||
|
||||
let { children, ...rest }: ComboboxViewportProps = $props();
|
||||
</script>
|
||||
|
||||
<Combobox.Viewport {...rest}>
|
||||
{@render children?.()}
|
||||
</Combobox.Viewport>
|
||||
@ -0,0 +1,420 @@
|
||||
[data-combobox] {
|
||||
display: inline-grid;
|
||||
gap: var(--space-2);
|
||||
inline-size: 100%;
|
||||
min-inline-size: 0;
|
||||
font-family: var(--font-ui);
|
||||
color: var(--color-content-primary);
|
||||
}
|
||||
|
||||
[data-combobox-control],
|
||||
[data-combobox-input],
|
||||
[data-combobox-trigger],
|
||||
[data-combobox-content] {
|
||||
--_combobox-accent-track: var(--color-primary-track);
|
||||
--_combobox-accent-border: var(--color-primary-border);
|
||||
--_combobox-accent-text: var(--color-primary-text);
|
||||
}
|
||||
|
||||
[data-combobox-control][data-color='secondary'],
|
||||
[data-combobox-input][data-color='secondary'],
|
||||
[data-combobox-trigger][data-color='secondary'],
|
||||
[data-combobox-content][data-color='secondary'] {
|
||||
--_combobox-accent-track: var(--color-secondary-track);
|
||||
--_combobox-accent-border: var(--color-secondary-border);
|
||||
--_combobox-accent-text: var(--color-secondary-text);
|
||||
}
|
||||
|
||||
[data-combobox-control][data-color='neutral'],
|
||||
[data-combobox-input][data-color='neutral'],
|
||||
[data-combobox-trigger][data-color='neutral'],
|
||||
[data-combobox-content][data-color='neutral'] {
|
||||
--_combobox-accent-track: var(--color-neutral-track);
|
||||
--_combobox-accent-border: var(--color-neutral-border);
|
||||
--_combobox-accent-text: var(--color-neutral-text);
|
||||
}
|
||||
|
||||
[data-combobox-control][data-color='affirm'],
|
||||
[data-combobox-input][data-color='affirm'],
|
||||
[data-combobox-trigger][data-color='affirm'],
|
||||
[data-combobox-content][data-color='affirm'] {
|
||||
--_combobox-accent-track: var(--color-affirm-track);
|
||||
--_combobox-accent-border: var(--color-affirm-border);
|
||||
--_combobox-accent-text: var(--color-affirm-text);
|
||||
}
|
||||
|
||||
[data-combobox-control][data-color='fulfill'],
|
||||
[data-combobox-input][data-color='fulfill'],
|
||||
[data-combobox-trigger][data-color='fulfill'],
|
||||
[data-combobox-content][data-color='fulfill'] {
|
||||
--_combobox-accent-track: var(--color-fulfill-track);
|
||||
--_combobox-accent-border: var(--color-fulfill-border);
|
||||
--_combobox-accent-text: var(--color-fulfill-text);
|
||||
}
|
||||
|
||||
[data-combobox-control][data-color='risk'],
|
||||
[data-combobox-input][data-color='risk'],
|
||||
[data-combobox-trigger][data-color='risk'],
|
||||
[data-combobox-content][data-color='risk'] {
|
||||
--_combobox-accent-track: var(--color-risk-track);
|
||||
--_combobox-accent-border: var(--color-risk-border);
|
||||
--_combobox-accent-text: var(--color-risk-text);
|
||||
}
|
||||
|
||||
[data-combobox-control][data-color='threat'],
|
||||
[data-combobox-input][data-color='threat'],
|
||||
[data-combobox-trigger][data-color='threat'],
|
||||
[data-combobox-content][data-color='threat'] {
|
||||
--_combobox-accent-track: var(--color-threat-track);
|
||||
--_combobox-accent-border: var(--color-threat-border);
|
||||
--_combobox-accent-text: var(--color-threat-text);
|
||||
}
|
||||
|
||||
[data-combobox-control][data-color='loss'],
|
||||
[data-combobox-input][data-color='loss'],
|
||||
[data-combobox-trigger][data-color='loss'],
|
||||
[data-combobox-content][data-color='loss'] {
|
||||
--_combobox-accent-track: var(--color-loss-track);
|
||||
--_combobox-accent-border: var(--color-loss-border);
|
||||
--_combobox-accent-text: var(--color-loss-text);
|
||||
}
|
||||
|
||||
[data-combobox-control] {
|
||||
--_combobox-control-height: var(--control-height-md);
|
||||
--_combobox-control-px: var(--space-3);
|
||||
--_combobox-control-gap: var(--space-2);
|
||||
--_combobox-font-size: var(--font-size-sm);
|
||||
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--_combobox-control-gap);
|
||||
inline-size: 100%;
|
||||
min-inline-size: 0;
|
||||
min-block-size: var(--_combobox-control-height);
|
||||
padding-inline: var(--_combobox-control-px);
|
||||
border: var(--border-width) solid var(--color-border-default);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-surface-default);
|
||||
box-shadow: var(--shadow-xs);
|
||||
color: var(--color-content-primary);
|
||||
font-size: var(--_combobox-font-size);
|
||||
line-height: var(--leading-ui);
|
||||
transition:
|
||||
background var(--duration-fast) var(--ease-default),
|
||||
border-color var(--duration-fast) var(--ease-default),
|
||||
box-shadow var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
[data-combobox-control][data-size='sm'] {
|
||||
--_combobox-control-height: var(--control-height-sm);
|
||||
--_combobox-control-px: var(--space-2-5);
|
||||
--_combobox-control-gap: var(--space-1-5);
|
||||
--_combobox-font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
[data-combobox-control][data-size='lg'] {
|
||||
--_combobox-control-height: var(--control-height-lg);
|
||||
--_combobox-control-px: var(--space-3-5);
|
||||
--_combobox-control-gap: var(--space-2-5);
|
||||
--_combobox-font-size: var(--font-size-md);
|
||||
}
|
||||
|
||||
[data-combobox-control][data-variant='outline'] {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-combobox-control][data-variant='ghost'] {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-combobox-control]:has([data-combobox-input]:focus-visible),
|
||||
[data-combobox-control]:has([data-combobox-trigger]:focus-visible),
|
||||
[data-combobox-control]:has([data-state='open']) {
|
||||
border-color: var(--_combobox-accent-border);
|
||||
box-shadow:
|
||||
0 0 0 var(--focus-ring-offset) var(--color-surface-default),
|
||||
0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color);
|
||||
}
|
||||
|
||||
[data-combobox-control]:has([data-disabled]),
|
||||
[data-combobox][data-disabled] [data-combobox-control] {
|
||||
border-color: var(--color-border-subtle);
|
||||
background: var(--color-surface-raised);
|
||||
color: var(--color-content-disabled);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-combobox-input] {
|
||||
box-sizing: border-box;
|
||||
flex: 1 1 auto;
|
||||
inline-size: 100%;
|
||||
min-inline-size: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
line-height: var(--leading-ui);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
[data-combobox-input]::placeholder {
|
||||
color: var(--color-content-muted);
|
||||
}
|
||||
|
||||
[data-combobox-input]:disabled {
|
||||
color: var(--color-content-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
[data-combobox-input]:not([data-combobox-control] [data-combobox-input]) {
|
||||
min-block-size: var(--control-height-md);
|
||||
padding-inline: var(--space-3);
|
||||
border: var(--border-width) solid var(--color-border-default);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-surface-default);
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
|
||||
[data-combobox-trigger] {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
inline-size: var(--control-height-xs);
|
||||
block-size: var(--control-height-xs);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: var(--radius-full);
|
||||
background: transparent;
|
||||
color: var(--color-content-secondary);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition:
|
||||
background var(--duration-fast) var(--ease-default),
|
||||
color var(--duration-fast) var(--ease-default),
|
||||
transform var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
[data-combobox-trigger]:hover:not(:disabled):not([data-disabled]) {
|
||||
background: var(--color-surface-raised);
|
||||
color: var(--color-content-primary);
|
||||
}
|
||||
|
||||
[data-combobox-trigger]:focus-visible {
|
||||
box-shadow:
|
||||
0 0 0 var(--focus-ring-offset) var(--color-surface-default),
|
||||
0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-ring-color);
|
||||
}
|
||||
|
||||
[data-combobox-trigger][data-disabled],
|
||||
[data-combobox-trigger]:disabled {
|
||||
color: var(--color-content-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
[data-combobox-indicator] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
inline-size: var(--icon-size-sm);
|
||||
block-size: var(--icon-size-sm);
|
||||
color: currentColor;
|
||||
transition: transform var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
[data-combobox-trigger][data-state='open'] [data-combobox-indicator] {
|
||||
transform: rotate(180deg);
|
||||
color: var(--_combobox-accent-text);
|
||||
}
|
||||
|
||||
[data-floating-wrapper]:has(> [data-combobox-content]) {
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
[data-combobox-content] {
|
||||
--_combobox-content-py: var(--space-1-5);
|
||||
--_combobox-content-px: var(--space-1-5);
|
||||
--_combobox-font-size: var(--font-size-sm);
|
||||
--_combobox-content-width: var(--_combobox-match-anchor-width, 14rem);
|
||||
|
||||
box-sizing: border-box;
|
||||
inline-size: min(var(--_combobox-content-width), calc(100vw - var(--space-4)));
|
||||
min-inline-size: min(14rem, calc(100vw - var(--space-4)));
|
||||
max-inline-size: min(28rem, calc(100vw - var(--space-4)));
|
||||
max-block-size: min(22rem, var(--floating-available-height, 100dvh));
|
||||
padding: var(--_combobox-content-py) var(--_combobox-content-px);
|
||||
overflow: hidden;
|
||||
border: var(--border-width) solid var(--color-border-default);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-surface-overlay);
|
||||
box-shadow: var(--shadow-lg);
|
||||
color: var(--color-content-primary);
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--_combobox-font-size);
|
||||
line-height: var(--leading-ui);
|
||||
outline: none;
|
||||
transform-origin: var(--floating-transform-origin, center);
|
||||
}
|
||||
|
||||
[data-combobox-content][data-size='sm'] {
|
||||
--_combobox-content-py: var(--space-1);
|
||||
--_combobox-content-px: var(--space-1);
|
||||
--_combobox-font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
[data-combobox-content][data-size='lg'] {
|
||||
--_combobox-content-py: var(--space-2);
|
||||
--_combobox-content-px: var(--space-2);
|
||||
--_combobox-font-size: var(--font-size-md);
|
||||
}
|
||||
|
||||
[data-combobox-content][data-variant='outline'] {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
[data-combobox-content][data-variant='ghost'] {
|
||||
border-color: transparent;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
[data-combobox-content][data-state='open'] {
|
||||
animation: combobox-content-in var(--duration-normal) var(--ease-out) both;
|
||||
}
|
||||
|
||||
[data-combobox-content][data-state='closed'] {
|
||||
animation: combobox-content-out var(--duration-fast) var(--ease-default) both;
|
||||
}
|
||||
|
||||
@keyframes combobox-content-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-2px) scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes combobox-content-out {
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(-2px) scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
[data-combobox-viewport] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-0-5);
|
||||
max-block-size: calc(22rem - (var(--_combobox-content-py) * 2));
|
||||
overflow: auto;
|
||||
padding: var(--space-0-5);
|
||||
}
|
||||
|
||||
[data-combobox-group] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-0-5);
|
||||
padding-block: var(--space-1);
|
||||
}
|
||||
|
||||
[data-combobox-group-heading] {
|
||||
padding: var(--space-1) var(--space-2);
|
||||
color: var(--color-content-muted);
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: var(--leading-ui);
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
[data-combobox-separator] {
|
||||
block-size: var(--border-width);
|
||||
margin-block: var(--space-1);
|
||||
background: var(--color-border-subtle);
|
||||
}
|
||||
|
||||
[data-combobox-item] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
min-inline-size: 0;
|
||||
min-block-size: var(--control-height-sm);
|
||||
padding-inline: var(--space-2);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-content-primary);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition:
|
||||
background var(--duration-fast) var(--ease-default),
|
||||
color var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
[data-combobox-item][data-highlighted] {
|
||||
background: var(--color-surface-raised);
|
||||
color: var(--color-content-primary);
|
||||
}
|
||||
|
||||
[data-combobox-item][data-state='checked'] {
|
||||
background: var(--_combobox-accent-track);
|
||||
color: var(--_combobox-accent-text);
|
||||
}
|
||||
|
||||
[data-combobox-item][data-disabled] {
|
||||
color: var(--color-content-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
[data-combobox-item-indicator] {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
inline-size: var(--icon-size-sm);
|
||||
block-size: var(--icon-size-sm);
|
||||
color: currentColor;
|
||||
opacity: 0;
|
||||
transform: scale(0.75);
|
||||
transition:
|
||||
opacity var(--duration-fast) var(--ease-default),
|
||||
transform var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
[data-combobox-item][data-state='checked'] [data-combobox-item-indicator] {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
[data-combobox-arrow] svg {
|
||||
display: block;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
[data-combobox-arrow] polygon {
|
||||
fill: var(--color-surface-overlay);
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
[data-combobox-arrow] path {
|
||||
stroke: var(--color-border-default);
|
||||
stroke-width: var(--border-width);
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
[data-combobox-control],
|
||||
[data-combobox-trigger],
|
||||
[data-combobox-indicator],
|
||||
[data-combobox-content],
|
||||
[data-combobox-item],
|
||||
[data-combobox-item-indicator] {
|
||||
animation: none;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
import * as Combobox from '$soma/components/combobox';
|
||||
import { setComboboxVisualContext } from './context.svelte';
|
||||
import type { ComboboxProps } from './types';
|
||||
|
||||
let {
|
||||
size = 'md',
|
||||
variant = 'surface',
|
||||
color = 'primary',
|
||||
value = $bindable([]),
|
||||
inputValue = $bindable(''),
|
||||
open = $bindable(false),
|
||||
children,
|
||||
...rest
|
||||
}: ComboboxProps = $props();
|
||||
|
||||
const eidos = ActiveEidos.require();
|
||||
const resolvedSize = $derived(eidos.resolve(size, 'md'));
|
||||
|
||||
setComboboxVisualContext({
|
||||
getSize: () => resolvedSize,
|
||||
getVariant: () => variant,
|
||||
getColor: () => color
|
||||
});
|
||||
</script>
|
||||
|
||||
<Combobox.Provider
|
||||
{...rest}
|
||||
bind:value
|
||||
bind:inputValue
|
||||
bind:open
|
||||
data-size={resolvedSize}
|
||||
data-variant={variant}
|
||||
data-color={color}
|
||||
>
|
||||
{@render children?.()}
|
||||
</Combobox.Provider>
|
||||
@ -0,0 +1,19 @@
|
||||
import { getContext, setContext } from 'svelte'
|
||||
import type { ComboboxColor, ComboboxSize, ComboboxVariant } from './types'
|
||||
|
||||
const COMBOBOX_VISUAL_CONTEXT = Symbol('uix.eidos.combobox.visual')
|
||||
|
||||
export interface ComboboxVisualContext {
|
||||
getSize(): ComboboxSize
|
||||
getVariant(): ComboboxVariant
|
||||
getColor(): ComboboxColor
|
||||
}
|
||||
|
||||
export function setComboboxVisualContext(ctx: ComboboxVisualContext): ComboboxVisualContext {
|
||||
setContext(COMBOBOX_VISUAL_CONTEXT, ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
export function getComboboxVisualContext(): ComboboxVisualContext | undefined {
|
||||
return getContext<ComboboxVisualContext | undefined>(COMBOBOX_VISUAL_CONTEXT)
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
import ComboboxComponent from './combobox.svelte'
|
||||
import Control from './combobox-control.svelte'
|
||||
import Input from './combobox-input.svelte'
|
||||
import Trigger from './combobox-trigger.svelte'
|
||||
import Indicator from './combobox-indicator.svelte'
|
||||
import Content from './combobox-content.svelte'
|
||||
import Viewport from './combobox-viewport.svelte'
|
||||
import Item from './combobox-item.svelte'
|
||||
import ItemIndicator from './combobox-item-indicator.svelte'
|
||||
import Group from './combobox-group.svelte'
|
||||
import GroupHeading from './combobox-group-heading.svelte'
|
||||
import Separator from './combobox-separator.svelte'
|
||||
import Arrow from './combobox-arrow.svelte'
|
||||
import { Portal } from '$soma/components/internal'
|
||||
|
||||
type ComboboxNamespace = typeof ComboboxComponent & {
|
||||
Control: typeof Control
|
||||
Input: typeof Input
|
||||
Trigger: typeof Trigger
|
||||
Indicator: typeof Indicator
|
||||
Portal: typeof Portal
|
||||
Content: typeof Content
|
||||
Viewport: typeof Viewport
|
||||
Item: typeof Item
|
||||
ItemIndicator: typeof ItemIndicator
|
||||
Group: typeof Group
|
||||
GroupHeading: typeof GroupHeading
|
||||
Separator: typeof Separator
|
||||
Arrow: typeof Arrow
|
||||
}
|
||||
|
||||
const Combobox = ComboboxComponent as ComboboxNamespace
|
||||
Combobox.Control = Control
|
||||
Combobox.Input = Input
|
||||
Combobox.Trigger = Trigger
|
||||
Combobox.Indicator = Indicator
|
||||
Combobox.Portal = Portal
|
||||
Combobox.Content = Content
|
||||
Combobox.Viewport = Viewport
|
||||
Combobox.Item = Item
|
||||
Combobox.ItemIndicator = ItemIndicator
|
||||
Combobox.Group = Group
|
||||
Combobox.GroupHeading = GroupHeading
|
||||
Combobox.Separator = Separator
|
||||
Combobox.Arrow = Arrow
|
||||
|
||||
export { Combobox }
|
||||
|
||||
export default Combobox
|
||||
|
||||
export type {
|
||||
ComboboxProps,
|
||||
ComboboxControlProps as ControlProps,
|
||||
ComboboxInputProps as InputProps,
|
||||
ComboboxTriggerProps as TriggerProps,
|
||||
ComboboxIndicatorProps as IndicatorProps,
|
||||
ComboboxContentProps as ContentProps,
|
||||
ComboboxViewportProps as ViewportProps,
|
||||
ComboboxItemProps as ItemProps,
|
||||
ComboboxItemIndicatorProps as ItemIndicatorProps,
|
||||
ComboboxGroupProps as GroupProps,
|
||||
ComboboxGroupHeadingProps as GroupHeadingProps,
|
||||
ComboboxSeparatorProps as SeparatorProps,
|
||||
ComboboxArrowProps as ArrowProps,
|
||||
ComboboxSize,
|
||||
ComboboxVariant,
|
||||
ComboboxColor
|
||||
} from './types'
|
||||
@ -0,0 +1,70 @@
|
||||
import type {
|
||||
ArrowProps,
|
||||
ContentProps,
|
||||
GroupHeadingProps,
|
||||
GroupProps,
|
||||
InputProps,
|
||||
ItemProps,
|
||||
ProviderProps,
|
||||
SeparatorProps,
|
||||
TriggerProps,
|
||||
ViewportProps
|
||||
} from '$soma/components/combobox'
|
||||
import type { ColorRole, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types'
|
||||
import type { Snippet } from 'svelte'
|
||||
import type { HTMLAttributes } from 'svelte/elements'
|
||||
|
||||
export type ComboboxSize = Extract<Size, 'sm' | 'md' | 'lg'>
|
||||
export type ComboboxVariant = ControlVariant
|
||||
export type ComboboxColor = ColorRole
|
||||
|
||||
export type ComboboxProps = ProviderProps & {
|
||||
/** Visual density for input, trigger, content and items. @default 'md' */
|
||||
size?: ResponsiveProp<ComboboxSize>
|
||||
/** Input/content treatment. @default 'surface' */
|
||||
variant?: ComboboxVariant
|
||||
/** Accent palette for focus, selected item and item indicator. @default 'primary' */
|
||||
color?: ComboboxColor
|
||||
}
|
||||
|
||||
export type ComboboxControlProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
||||
children?: Snippet
|
||||
size?: ResponsiveProp<ComboboxSize>
|
||||
variant?: ComboboxVariant
|
||||
color?: ComboboxColor
|
||||
}
|
||||
|
||||
export type ComboboxInputProps = InputProps & {
|
||||
size?: ResponsiveProp<ComboboxSize>
|
||||
variant?: ComboboxVariant
|
||||
color?: ComboboxColor
|
||||
}
|
||||
|
||||
export type ComboboxTriggerProps = TriggerProps & {
|
||||
size?: ResponsiveProp<ComboboxSize>
|
||||
variant?: ComboboxVariant
|
||||
color?: ComboboxColor
|
||||
}
|
||||
|
||||
export type ComboboxIndicatorProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & {
|
||||
children?: Snippet
|
||||
}
|
||||
|
||||
export type ComboboxContentProps = ContentProps & {
|
||||
size?: ResponsiveProp<ComboboxSize>
|
||||
variant?: ComboboxVariant
|
||||
color?: ComboboxColor
|
||||
/**
|
||||
* Match the floating panel's inline size to the input/control anchor.
|
||||
* @default true
|
||||
*/
|
||||
matchAnchorWidth?: ResponsiveProp<boolean>
|
||||
}
|
||||
|
||||
export type ComboboxViewportProps = ViewportProps
|
||||
export type ComboboxItemProps = ItemProps
|
||||
export type ComboboxItemIndicatorProps = ComboboxIndicatorProps
|
||||
export type ComboboxGroupProps = GroupProps
|
||||
export type ComboboxGroupHeadingProps = GroupHeadingProps
|
||||
export type ComboboxSeparatorProps = SeparatorProps
|
||||
export type ComboboxArrowProps = ArrowProps
|
||||
@ -0,0 +1,74 @@
|
||||
# Eidos DateField
|
||||
|
||||
Fecha de revision: 2026-05-19.
|
||||
|
||||
## Baseline
|
||||
|
||||
Air no tenia un `DateField` equivalente en la rama anterior. La referencia local
|
||||
real es Soma `date-field`, que ya implementa la edicion segmentada, y la receta
|
||||
Eidos solo envuelve esa superficie con tamanos, variante visual y paleta de
|
||||
intents.
|
||||
|
||||
## Superficie
|
||||
|
||||
```svelte
|
||||
<DateField bind:value placeholder={today} size="md" variant="surface" color="primary">
|
||||
<DateField.Label>Arrival</DateField.Label>
|
||||
<DateField.Input name="arrival">
|
||||
{#snippet children({ segments })}
|
||||
{#each segments as { part, value } (part + value)}
|
||||
<DateField.Segment {part}>{value}</DateField.Segment>
|
||||
{/each}
|
||||
{/snippet}
|
||||
</DateField.Input>
|
||||
</DateField>
|
||||
```
|
||||
|
||||
Partes publicas: `Label`, `Input`, `Segment`, `HiddenInput`.
|
||||
|
||||
## Comparativa
|
||||
|
||||
| Capacidad | Soma/Eidos | React Aria | Ark UI | Bits UI |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Input segmentado | Si | Si | Si | Si |
|
||||
| Dia/mes/ano | Si | Si | Si | Si |
|
||||
| Hora/minuto/segundo | Si | Si | Si | Si |
|
||||
| AM/PM y zona horaria | Si | Si | Si | Si |
|
||||
| `granularity` | Si | Si | Si | Si |
|
||||
| `hideTimeZone` | Si | Si | Si | Si |
|
||||
| `minValue` / `maxValue` | Si | Si | Si | Si |
|
||||
| Validacion custom | Si | Si | Si | Si |
|
||||
| Segmentos readonly | Si | No | No | Si |
|
||||
| Input hidden para forms | Si | Via DateInput | Si | Si |
|
||||
| Integracion con Field | Si | Via Form/TextField | Via Field | Manual |
|
||||
| Variante/tamano/paleta visual | Si, Eidos | CSS externo | CSS externo | CSS externo |
|
||||
|
||||
## Decisiones
|
||||
|
||||
- No se exporta `Provider` ni `Root` publico. La forma Eidos es `<DateField>`
|
||||
con hijos attached.
|
||||
- Eidos no implementa logica de calendario ni popover. Eso pertenece a
|
||||
`DatePicker`, no a `DateField`.
|
||||
- `HiddenInput` se mantiene como parte publica por simetria con Soma, pero el
|
||||
caso normal ya lo renderiza `DateField.Input` cuando recibe `name`.
|
||||
- `size`, `variant` y `color` pertenecen a Eidos porque solo afectan receta
|
||||
visual; `value`, `placeholder`, `granularity`, `locale`, `dir`, validacion y
|
||||
navegacion de segmentos pertenecen a Soma.
|
||||
- Los eventos declarados por Morfo son `commit-set` y `commit-clear`; la
|
||||
edicion continua de segmentos no se modela como evento perceptivo por cada
|
||||
pulsacion.
|
||||
|
||||
## Gaps
|
||||
|
||||
| Gap | Decision |
|
||||
| --- | --- |
|
||||
| Popover/calendario | Fuera de `DateField`; debe vivir en `DatePicker`. |
|
||||
| Formatting visual extra | Usar `locale`, `hourCycle` y `granularity` de Soma. |
|
||||
| Rango visual doble | Fuera de este componente; pertenece a range/date picker. |
|
||||
|
||||
## Referencias
|
||||
|
||||
- Soma DateField: `src/uix/soma/components/date-field/README.md`
|
||||
- React Aria DateField: https://react-spectrum.adobe.com/react-aria/DateField.html
|
||||
- Ark UI Date Picker / Field: https://ark-ui.com/docs/components/date-picker
|
||||
- Bits UI Date Field: https://bits-ui.com/docs/components/date-field
|
||||
@ -0,0 +1,5 @@
|
||||
<script lang="ts">
|
||||
import * as DateField from '$soma/components/date-field';
|
||||
</script>
|
||||
|
||||
<DateField.HiddenInput />
|
||||
@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import * as DateField from '$soma/components/date-field';
|
||||
import type { DateFieldInputProps, DateFieldInputSnippetProps } from './types';
|
||||
|
||||
let { children: bodyContent, ...rest }: DateFieldInputProps = $props();
|
||||
</script>
|
||||
|
||||
<DateField.Input {...rest}>
|
||||
{#snippet children(args: DateFieldInputSnippetProps)}
|
||||
{@render bodyContent?.(args)}
|
||||
{/snippet}
|
||||
</DateField.Input>
|
||||
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as DateField from '$soma/components/date-field';
|
||||
import type { DateFieldLabelProps } from './types';
|
||||
|
||||
let { children, ...rest }: DateFieldLabelProps = $props();
|
||||
</script>
|
||||
|
||||
<DateField.Label {...rest}>
|
||||
{@render children?.()}
|
||||
</DateField.Label>
|
||||
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as DateField from '$soma/components/date-field';
|
||||
import type { DateFieldSegmentProps } from './types';
|
||||
|
||||
let { children, ...rest }: DateFieldSegmentProps = $props();
|
||||
</script>
|
||||
|
||||
<DateField.Segment {...rest}>
|
||||
{@render children?.()}
|
||||
</DateField.Segment>
|
||||
@ -0,0 +1,181 @@
|
||||
[data-date-field] {
|
||||
--_date-field-height: var(--date-field-height-md);
|
||||
--_date-field-px: var(--date-field-px-md);
|
||||
--_date-field-gap: var(--date-field-gap-md);
|
||||
--_date-field-segment-px: var(--date-field-segment-px-md);
|
||||
--_date-field-font-size: var(--date-field-font-size-md);
|
||||
--_date-field-accent-border: var(--color-primary-border);
|
||||
--_date-field-accent-track: var(--color-primary-track);
|
||||
--_date-field-accent-text: var(--color-primary-text);
|
||||
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
gap: var(--date-field-stack-gap);
|
||||
inline-size: 100%;
|
||||
min-inline-size: 0;
|
||||
color: var(--date-field-color);
|
||||
font-family: var(--date-field-font-family);
|
||||
font-size: var(--_date-field-font-size);
|
||||
line-height: var(--date-field-line-height);
|
||||
}
|
||||
|
||||
[data-date-field][data-size='sm'] {
|
||||
--_date-field-height: var(--date-field-height-sm);
|
||||
--_date-field-px: var(--date-field-px-sm);
|
||||
--_date-field-gap: var(--date-field-gap-sm);
|
||||
--_date-field-segment-px: var(--date-field-segment-px-sm);
|
||||
--_date-field-font-size: var(--date-field-font-size-sm);
|
||||
}
|
||||
|
||||
[data-date-field][data-size='lg'] {
|
||||
--_date-field-height: var(--date-field-height-lg);
|
||||
--_date-field-px: var(--date-field-px-lg);
|
||||
--_date-field-gap: var(--date-field-gap-lg);
|
||||
--_date-field-segment-px: var(--date-field-segment-px-lg);
|
||||
--_date-field-font-size: var(--date-field-font-size-lg);
|
||||
}
|
||||
|
||||
[data-date-field][data-color='secondary'] {
|
||||
--_date-field-accent-border: var(--color-secondary-border);
|
||||
--_date-field-accent-track: var(--color-secondary-track);
|
||||
--_date-field-accent-text: var(--color-secondary-text);
|
||||
}
|
||||
|
||||
[data-date-field][data-color='neutral'] {
|
||||
--_date-field-accent-border: var(--color-neutral-border);
|
||||
--_date-field-accent-track: var(--color-neutral-track);
|
||||
--_date-field-accent-text: var(--color-neutral-text);
|
||||
}
|
||||
|
||||
[data-date-field][data-color='affirm'] {
|
||||
--_date-field-accent-border: var(--color-affirm-border);
|
||||
--_date-field-accent-track: var(--color-affirm-track);
|
||||
--_date-field-accent-text: var(--color-affirm-text);
|
||||
}
|
||||
|
||||
[data-date-field][data-color='fulfill'] {
|
||||
--_date-field-accent-border: var(--color-fulfill-border);
|
||||
--_date-field-accent-track: var(--color-fulfill-track);
|
||||
--_date-field-accent-text: var(--color-fulfill-text);
|
||||
}
|
||||
|
||||
[data-date-field][data-color='risk'] {
|
||||
--_date-field-accent-border: var(--color-risk-border);
|
||||
--_date-field-accent-track: var(--color-risk-track);
|
||||
--_date-field-accent-text: var(--color-risk-text);
|
||||
}
|
||||
|
||||
[data-date-field][data-color='threat'] {
|
||||
--_date-field-accent-border: var(--color-threat-border);
|
||||
--_date-field-accent-track: var(--color-threat-track);
|
||||
--_date-field-accent-text: var(--color-threat-text);
|
||||
}
|
||||
|
||||
[data-date-field][data-color='loss'] {
|
||||
--_date-field-accent-border: var(--color-loss-border);
|
||||
--_date-field-accent-track: var(--color-loss-track);
|
||||
--_date-field-accent-text: var(--color-loss-text);
|
||||
}
|
||||
|
||||
[data-date-field-label] {
|
||||
color: var(--date-field-label-color);
|
||||
font: inherit;
|
||||
font-weight: var(--date-field-label-font-weight);
|
||||
}
|
||||
|
||||
[data-date-field-input] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--_date-field-gap);
|
||||
min-inline-size: 0;
|
||||
inline-size: 100%;
|
||||
min-block-size: var(--_date-field-height);
|
||||
padding-inline: var(--_date-field-px);
|
||||
border: var(--date-field-border-width) solid var(--date-field-border);
|
||||
border-radius: var(--date-field-radius);
|
||||
background: var(--date-field-bg);
|
||||
color: inherit;
|
||||
transition:
|
||||
background var(--date-field-transition-duration) var(--date-field-transition-ease),
|
||||
border-color var(--date-field-transition-duration) var(--date-field-transition-ease),
|
||||
box-shadow var(--date-field-transition-duration) var(--date-field-transition-ease);
|
||||
}
|
||||
|
||||
[data-date-field][data-variant='outline'] [data-date-field-input] {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
[data-date-field][data-variant='ghost'] [data-date-field-input] {
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
[data-date-field-input]:has([data-date-field-segment]:focus-visible) {
|
||||
border-color: var(--_date-field-accent-border);
|
||||
box-shadow: var(--date-field-focus-shadow);
|
||||
}
|
||||
|
||||
[data-date-field][data-invalid] [data-date-field-input],
|
||||
[data-date-field-input][data-invalid] {
|
||||
border-color: var(--date-field-border-invalid);
|
||||
}
|
||||
|
||||
[data-date-field][data-disabled],
|
||||
[data-date-field-input][data-disabled] {
|
||||
opacity: var(--date-field-disabled-opacity);
|
||||
}
|
||||
|
||||
[data-date-field-segment] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-inline-size: var(--date-field-segment-min-width);
|
||||
block-size: var(--date-field-segment-height);
|
||||
padding-inline: var(--_date-field-segment-px);
|
||||
border-radius: var(--date-field-segment-radius);
|
||||
color: var(--date-field-segment-color);
|
||||
font: inherit;
|
||||
outline: none;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
background var(--date-field-transition-duration) var(--date-field-transition-ease),
|
||||
color var(--date-field-transition-duration) var(--date-field-transition-ease),
|
||||
box-shadow var(--date-field-transition-duration) var(--date-field-transition-ease);
|
||||
}
|
||||
|
||||
[data-date-field-segment][data-segment='literal'] {
|
||||
min-inline-size: auto;
|
||||
padding-inline: 0;
|
||||
color: var(--date-field-literal-color);
|
||||
}
|
||||
|
||||
[data-date-field-segment][data-placeholder] {
|
||||
color: var(--date-field-placeholder-color);
|
||||
}
|
||||
|
||||
[data-date-field-segment]:focus-visible {
|
||||
background: var(--_date-field-accent-track);
|
||||
color: var(--_date-field-accent-text);
|
||||
box-shadow: var(--date-field-segment-focus-shadow);
|
||||
}
|
||||
|
||||
[data-date-field-segment][data-invalid] {
|
||||
color: var(--date-field-segment-invalid-color);
|
||||
}
|
||||
|
||||
[data-date-field-segment][data-readonly] {
|
||||
background: var(--date-field-segment-readonly-bg);
|
||||
}
|
||||
|
||||
[data-date-field-segment][data-disabled] {
|
||||
cursor: not-allowed;
|
||||
opacity: var(--date-field-disabled-opacity);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
[data-date-field-input],
|
||||
[data-date-field-segment] {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
import * as DateField from '$soma/components/date-field';
|
||||
import type { DateFieldProps } from './types';
|
||||
|
||||
let {
|
||||
size = 'md',
|
||||
variant = 'surface',
|
||||
color = 'primary',
|
||||
value = $bindable(),
|
||||
placeholder = $bindable(),
|
||||
children: bodyContent,
|
||||
...rest
|
||||
}: DateFieldProps = $props();
|
||||
|
||||
const eidos = ActiveEidos.require();
|
||||
const resolvedSize = $derived(eidos.resolve(size, 'md'));
|
||||
</script>
|
||||
|
||||
<DateField.Provider
|
||||
{...rest}
|
||||
bind:value
|
||||
bind:placeholder
|
||||
data-size={resolvedSize}
|
||||
data-variant={variant}
|
||||
data-color={color}
|
||||
>
|
||||
{@render bodyContent?.()}
|
||||
</DateField.Provider>
|
||||
@ -0,0 +1,34 @@
|
||||
import DateFieldComponent from './date-field.svelte'
|
||||
import Label from './date-field-label.svelte'
|
||||
import Input from './date-field-input.svelte'
|
||||
import Segment from './date-field-segment.svelte'
|
||||
import HiddenInput from './date-field-hidden-input.svelte'
|
||||
|
||||
type DateFieldNamespace = typeof DateFieldComponent & {
|
||||
Label: typeof Label
|
||||
Input: typeof Input
|
||||
Segment: typeof Segment
|
||||
HiddenInput: typeof HiddenInput
|
||||
}
|
||||
|
||||
const DateField = DateFieldComponent as DateFieldNamespace
|
||||
DateField.Label = Label
|
||||
DateField.Input = Input
|
||||
DateField.Segment = Segment
|
||||
DateField.HiddenInput = HiddenInput
|
||||
|
||||
export { DateField }
|
||||
|
||||
export default DateField
|
||||
|
||||
export type {
|
||||
DateFieldProps,
|
||||
DateFieldLabelProps as LabelProps,
|
||||
DateFieldInputProps as InputProps,
|
||||
DateFieldSegmentProps as SegmentProps,
|
||||
DateFieldHiddenInputProps as HiddenInputProps,
|
||||
InputSnippetProps,
|
||||
DateFieldSize,
|
||||
DateFieldVariant,
|
||||
DateFieldColor
|
||||
} from './types'
|
||||
@ -0,0 +1,28 @@
|
||||
import type {
|
||||
InputProps,
|
||||
InputSnippetProps as DateFieldInputSnippetProps,
|
||||
LabelProps,
|
||||
ProviderProps,
|
||||
SegmentProps
|
||||
} from '$soma/components/date-field'
|
||||
import type { ColorRole, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types'
|
||||
|
||||
export type DateFieldSize = Extract<Size, 'sm' | 'md' | 'lg'>
|
||||
export type DateFieldVariant = ControlVariant
|
||||
export type DateFieldColor = ColorRole
|
||||
|
||||
export type DateFieldProps = ProviderProps & {
|
||||
/** Visual size for input height, segment padding and text. @default 'md' */
|
||||
size?: ResponsiveProp<DateFieldSize>
|
||||
/** Visual treatment of the segmented input. @default 'surface' */
|
||||
variant?: DateFieldVariant
|
||||
/** Intent palette for focus affordance. @default 'primary' */
|
||||
color?: DateFieldColor
|
||||
}
|
||||
|
||||
export type DateFieldLabelProps = LabelProps
|
||||
export type DateFieldInputProps = InputProps
|
||||
export type DateFieldSegmentProps = SegmentProps
|
||||
export type DateFieldHiddenInputProps = Record<string, never>
|
||||
|
||||
export type { DateFieldInputSnippetProps, DateFieldInputSnippetProps as InputSnippetProps }
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Area as SomaEditableArea } from '$soma/components/editable';
|
||||
import * as Editable from '$soma/components/editable';
|
||||
import type { EditableAreaProps } from './types';
|
||||
|
||||
let { children, ...rest }: EditableAreaProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaEditableArea {...rest}>
|
||||
<Editable.Area {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaEditableArea>
|
||||
</Editable.Area>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { CancelTrigger as SomaEditableCancelTrigger } from '$soma/components/editable';
|
||||
import * as Editable from '$soma/components/editable';
|
||||
import type { EditableCancelTriggerProps } from './types';
|
||||
|
||||
let { children, ...rest }: EditableCancelTriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaEditableCancelTrigger {...rest}>
|
||||
<Editable.CancelTrigger {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaEditableCancelTrigger>
|
||||
</Editable.CancelTrigger>
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as Editable from '$soma/components/editable';
|
||||
import type { EditableControlProps } from './types';
|
||||
|
||||
let { children, ...rest }: EditableControlProps = $props();
|
||||
</script>
|
||||
|
||||
<Editable.Control {...rest}>
|
||||
{@render children?.()}
|
||||
</Editable.Control>
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { EditTrigger as SomaEditableEditTrigger } from '$soma/components/editable';
|
||||
import * as Editable from '$soma/components/editable';
|
||||
import type { EditableEditTriggerProps } from './types';
|
||||
|
||||
let { children, ...rest }: EditableEditTriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaEditableEditTrigger {...rest}>
|
||||
<Editable.EditTrigger {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaEditableEditTrigger>
|
||||
</Editable.EditTrigger>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Input as SomaEditableInput } from '$soma/components/editable';
|
||||
import * as Editable from '$soma/components/editable';
|
||||
import type { EditableInputProps } from './types';
|
||||
|
||||
let { children, ...rest }: EditableInputProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaEditableInput {...rest}>
|
||||
<Editable.Input {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaEditableInput>
|
||||
</Editable.Input>
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { Preview as SomaEditablePreview } from '$soma/components/editable';
|
||||
import * as Editable from '$soma/components/editable';
|
||||
import type { EditablePreviewProps } from './types';
|
||||
|
||||
let { children, ...rest }: EditablePreviewProps = $props();
|
||||
let { children: bodyContent, ...rest }: EditablePreviewProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaEditablePreview {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaEditablePreview>
|
||||
{#if bodyContent}
|
||||
<Editable.Preview {...rest}>
|
||||
{@render bodyContent()}
|
||||
</Editable.Preview>
|
||||
{:else}
|
||||
<Editable.Preview {...rest} />
|
||||
{/if}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { SubmitTrigger as SomaEditableSubmitTrigger } from '$soma/components/editable';
|
||||
import * as Editable from '$soma/components/editable';
|
||||
import type { EditableSubmitTriggerProps } from './types';
|
||||
|
||||
let { children, ...rest }: EditableSubmitTriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaEditableSubmitTrigger {...rest}>
|
||||
<Editable.SubmitTrigger {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaEditableSubmitTrigger>
|
||||
</Editable.SubmitTrigger>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Control as SomaFieldControl } from '$soma/components/field';
|
||||
import * as Field from '$soma/components/field';
|
||||
import type { FieldControlProps } from './types';
|
||||
|
||||
let { children, ...rest }: FieldControlProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFieldControl {...rest}>
|
||||
<Field.Control {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaFieldControl>
|
||||
</Field.Control>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { ErrorText as SomaFieldErrorText } from '$soma/components/field';
|
||||
import * as Field from '$soma/components/field';
|
||||
import type { FieldErrorTextProps } from './types';
|
||||
|
||||
let { children, ...rest }: FieldErrorTextProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFieldErrorText {...rest}>
|
||||
<Field.ErrorText {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaFieldErrorText>
|
||||
</Field.ErrorText>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { HelperText as SomaFieldHelperText } from '$soma/components/field';
|
||||
import * as Field from '$soma/components/field';
|
||||
import type { FieldHelperTextProps } from './types';
|
||||
|
||||
let { children, ...rest }: FieldHelperTextProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFieldHelperText {...rest}>
|
||||
<Field.HelperText {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaFieldHelperText>
|
||||
</Field.HelperText>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Input as SomaFieldInput } from '$soma/components/field';
|
||||
import * as Field from '$soma/components/field';
|
||||
import type { FieldInputProps } from './types';
|
||||
|
||||
let { value = $bindable(''), children, ...rest }: FieldInputProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFieldInput {...rest} bind:value>
|
||||
<Field.Input {...rest} bind:value>
|
||||
{@render children?.()}
|
||||
</SomaFieldInput>
|
||||
</Field.Input>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Label as SomaFieldLabel } from '$soma/components/field';
|
||||
import * as Field from '$soma/components/field';
|
||||
import type { FieldLabelProps } from './types';
|
||||
|
||||
let { children, ...rest }: FieldLabelProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFieldLabel {...rest}>
|
||||
<Field.Label {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaFieldLabel>
|
||||
</Field.Label>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Prefix as SomaFieldPrefix } from '$soma/components/field';
|
||||
import * as Field from '$soma/components/field';
|
||||
import type { FieldPrefixProps } from './types';
|
||||
|
||||
let { children, ...rest }: FieldPrefixProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFieldPrefix {...rest}>
|
||||
<Field.Prefix {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaFieldPrefix>
|
||||
</Field.Prefix>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { RequiredIndicator as SomaFieldRequiredIndicator } from '$soma/components/field';
|
||||
import * as Field from '$soma/components/field';
|
||||
import type { FieldRequiredIndicatorProps } from './types';
|
||||
|
||||
let { children, ...rest }: FieldRequiredIndicatorProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFieldRequiredIndicator {...rest}>
|
||||
<Field.RequiredIndicator {...rest}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
*
|
||||
{/if}
|
||||
</SomaFieldRequiredIndicator>
|
||||
</Field.RequiredIndicator>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Suffix as SomaFieldSuffix } from '$soma/components/field';
|
||||
import * as Field from '$soma/components/field';
|
||||
import type { FieldSuffixProps } from './types';
|
||||
|
||||
let { children, ...rest }: FieldSuffixProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFieldSuffix {...rest}>
|
||||
<Field.Suffix {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaFieldSuffix>
|
||||
</Field.Suffix>
|
||||
|
||||
@ -1,20 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
/**
|
||||
* Eidos `<Field>` — visual wrapper over Soma's form field primitive.
|
||||
* Soma owns state, form registration and ARIA; Eidos adds size/variant.
|
||||
* Soma owns state, form registration and ARIA; Eidos adds visual attrs.
|
||||
*/
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
import { Provider as SomaFieldProvider } from '$soma/components/field';
|
||||
import * as Field from '$soma/components/field';
|
||||
import type { FieldProps } from './types';
|
||||
|
||||
let { size = 'md', variant = 'outline', children: bodyContent, ...rest }: FieldProps = $props();
|
||||
let {
|
||||
size = 'md',
|
||||
variant = 'outline',
|
||||
orientation = 'vertical',
|
||||
color = 'primary',
|
||||
children: bodyContent,
|
||||
...rest
|
||||
}: FieldProps = $props();
|
||||
|
||||
const eidos = ActiveEidos.require();
|
||||
const resolvedSize = $derived(eidos.resolve(size, 'md'));
|
||||
</script>
|
||||
|
||||
<SomaFieldProvider {...rest} data-size={resolvedSize} data-variant={variant}>
|
||||
<Field.Provider
|
||||
{...rest}
|
||||
data-size={resolvedSize}
|
||||
data-variant={variant}
|
||||
data-orientation={orientation}
|
||||
data-color={color}
|
||||
>
|
||||
{#snippet children(snippetProps)}
|
||||
{@render bodyContent?.(snippetProps)}
|
||||
{/snippet}
|
||||
</SomaFieldProvider>
|
||||
</Field.Provider>
|
||||
|
||||
@ -1,39 +1,45 @@
|
||||
import type {
|
||||
ControlProps as SomaFieldControlProps,
|
||||
ErrorTextProps as SomaFieldErrorTextProps,
|
||||
HelperTextProps as SomaFieldHelperTextProps,
|
||||
InputProps as SomaFieldInputProps,
|
||||
LabelProps as SomaFieldLabelProps,
|
||||
PrefixProps as SomaFieldPrefixProps,
|
||||
ProviderProps as SomaFieldProviderProps,
|
||||
ControlProps,
|
||||
ErrorTextProps,
|
||||
HelperTextProps,
|
||||
InputProps,
|
||||
LabelProps,
|
||||
PrefixProps,
|
||||
ProviderProps,
|
||||
FieldSnippetProps as FieldProviderSnippetProps,
|
||||
RequiredIndicatorProps as SomaFieldRequiredIndicatorProps,
|
||||
SuffixProps as SomaFieldSuffixProps
|
||||
RequiredIndicatorProps,
|
||||
SuffixProps
|
||||
} from '$soma/components/field';
|
||||
import type { ResponsiveProp, Size } from '$uix/eidos/lib/types';
|
||||
import type { ColorRole, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types';
|
||||
|
||||
export type FieldSize = Extract<Size, 'xs' | 'sm' | 'md' | 'lg'>;
|
||||
export type FieldVariant = 'outline' | 'ghost';
|
||||
export type FieldVariant = Extract<ControlVariant, 'outline' | 'ghost'>;
|
||||
export type FieldOrientation = 'vertical' | 'horizontal' | 'responsive';
|
||||
export type FieldColor = ColorRole;
|
||||
|
||||
/**
|
||||
* Props for the eidos `<Field>`.
|
||||
*
|
||||
* Soma owns state, form registration and ARIA wiring. Eidos adds visual size
|
||||
* and variant only.
|
||||
* Soma owns state, form registration and ARIA wiring. Eidos adds visual
|
||||
* size, variant, orientation and intent color.
|
||||
*/
|
||||
export type FieldProps = SomaFieldProviderProps & {
|
||||
export type FieldProps = ProviderProps & {
|
||||
/** Visual size. Accepts a responsive map resolved by ActiveEidos. @default 'md' */
|
||||
size?: ResponsiveProp<FieldSize>;
|
||||
/** Visual variant for the control shell. @default 'outline' */
|
||||
variant?: FieldVariant;
|
||||
/** Label/control layout. @default 'vertical' */
|
||||
orientation?: FieldOrientation;
|
||||
/** Accent palette for focus and required indicator. @default 'primary' */
|
||||
color?: FieldColor;
|
||||
};
|
||||
|
||||
export type FieldLabelProps = SomaFieldLabelProps;
|
||||
export type FieldRequiredIndicatorProps = SomaFieldRequiredIndicatorProps;
|
||||
export type FieldControlProps = SomaFieldControlProps;
|
||||
export type FieldInputProps = SomaFieldInputProps;
|
||||
export type FieldHelperTextProps = SomaFieldHelperTextProps;
|
||||
export type FieldErrorTextProps = SomaFieldErrorTextProps;
|
||||
export type FieldPrefixProps = SomaFieldPrefixProps;
|
||||
export type FieldSuffixProps = SomaFieldSuffixProps;
|
||||
export type FieldLabelProps = LabelProps;
|
||||
export type FieldRequiredIndicatorProps = RequiredIndicatorProps;
|
||||
export type FieldControlProps = ControlProps;
|
||||
export type FieldInputProps = InputProps;
|
||||
export type FieldHelperTextProps = HelperTextProps;
|
||||
export type FieldErrorTextProps = ErrorTextProps;
|
||||
export type FieldPrefixProps = PrefixProps;
|
||||
export type FieldSuffixProps = SuffixProps;
|
||||
export type { FieldProviderSnippetProps };
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { ClearTrigger as SomaFileUploadClearTrigger } from '$soma/components/file-upload';
|
||||
import * as FileUpload from '$soma/components/file-upload';
|
||||
import type { FileUploadClearTriggerProps } from './types';
|
||||
|
||||
let { children, ...rest }: FileUploadClearTriggerProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFileUploadClearTrigger {...rest}>
|
||||
<FileUpload.ClearTrigger {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaFileUploadClearTrigger>
|
||||
</FileUpload.ClearTrigger>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Dropzone as SomaFileUploadDropzone } from '$soma/components/file-upload';
|
||||
import * as FileUpload from '$soma/components/file-upload';
|
||||
import type { FileUploadDropzoneProps } from './types';
|
||||
|
||||
let { children, ...rest }: FileUploadDropzoneProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFileUploadDropzone {...rest}>
|
||||
<FileUpload.Dropzone {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaFileUploadDropzone>
|
||||
</FileUpload.Dropzone>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { FileList as SomaFileUploadFileList } from '$soma/components/file-upload';
|
||||
import * as FileUpload from '$soma/components/file-upload';
|
||||
import type { FileUploadFileListProps } from './types';
|
||||
|
||||
let { children, ...rest }: FileUploadFileListProps = $props();
|
||||
</script>
|
||||
|
||||
<SomaFileUploadFileList {...rest}>
|
||||
<FileUpload.FileList {...rest}>
|
||||
{@render children?.()}
|
||||
</SomaFileUploadFileList>
|
||||
</FileUpload.FileList>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue