Add Eidos pagination wrapper

active-uix
dev 5 months ago
parent bdaae718ad
commit f62b32677c

@ -46,9 +46,21 @@ Actualizacion Eidos component migration 2026-05-17:
- `npm run check` -> 0 errores, 0 warnings. - `npm run check` -> 0 errores, 0 warnings.
- `node --import tsx/esm scripts/eidos-lint-all.ts` -> 0 invalid, sin drift - `node --import tsx/esm scripts/eidos-lint-all.ts` -> 0 invalid, sin drift
hotspots. hotspots.
- Tercera tanda del mismo bloque:
- `Pagination`: root visual `pagination.svelte`, partes `PrevTrigger`,
`NextTrigger`, `Item`, `Ellipsis`, `pagination.css`, `types.ts` e
`index.ts`.
- El root conserva el snippet de Soma con `{ pages, totalPages }` y solo
anade `size` visual resuelto por `ActiveEidos`.
- La receta cubre controles, estado selected, disabled, ellipsis y focus.
- Validado despues de `Pagination`:
- `npx vitest run src/uix/eidos` -> 7 archivos, 84 tests OK.
- `npm run check` -> 0 errores, 0 warnings.
- `node --import tsx/esm scripts/eidos-lint-all.ts` -> 0 invalid, sin drift
hotspots.
- Siguiente bloque sugerido: - Siguiente bloque sugerido:
- Migrar otro grupo de bajo riesgo desde Soma a Eidos: candidatos reales por - Migrar otro grupo de bajo riesgo desde Soma a Eidos: candidatos reales por
morfo/soma existentes son `pagination`, `rating-group` o `number-field`. morfo/soma existentes son `rating-group` o `number-field`.
- Antes de tocar componentes grandes (`select`, `combobox`, `calendar`, - Antes de tocar componentes grandes (`select`, `combobox`, `calendar`,
`date-field`) conviene fijar una tabla por componente: partes Soma, `date-field`) conviene fijar una tabla por componente: partes Soma,
props visuales Eidos, recipe tokens y attrs esperados. props visuales Eidos, recipe tokens y attrs esperados.

@ -17,6 +17,7 @@ const VISUAL_ATTRS = [
['drawer/drawer-close.svelte', ['data-icon-only']], ['drawer/drawer-close.svelte', ['data-icon-only']],
['drawer/drawer-content.svelte', ['data-size']], ['drawer/drawer-content.svelte', ['data-size']],
['meter/meter.svelte', ['data-size']], ['meter/meter.svelte', ['data-size']],
['pagination/pagination.svelte', ['data-size']],
['popover/popover-close.svelte', ['data-icon-only']], ['popover/popover-close.svelte', ['data-icon-only']],
['popover/popover-content.svelte', ['data-size']], ['popover/popover-content.svelte', ['data-size']],
['progress/progress.svelte', ['data-size']], ['progress/progress.svelte', ['data-size']],

@ -355,6 +355,7 @@ su subdirectorio.
| avatar | ✅ multi-part | Image, Fallback (eidos-native) | | avatar | ✅ multi-part | Image, Fallback (eidos-native) |
| icon | ✅ single-part | + 1697 lucide glyphs | | icon | ✅ single-part | + 1697 lucide glyphs |
| meter | ✅ multi-part | Indicator | | meter | ✅ multi-part | Indicator |
| pagination | ✅ multi-part | PrevTrigger, NextTrigger, Item, Ellipsis |
| progress | ✅ multi-part | Indicator | | progress | ✅ multi-part | Indicator |
| slider | ✅ multi-part | Range, Thumb, Tick | | slider | ✅ multi-part | Range, Thumb, Tick |
| tooltip | ✅ multi-part | + Group | | tooltip | ✅ multi-part | + Group |

@ -0,0 +1,45 @@
// Pagination — eidos compound API, disciplined option C.
//
// import { Pagination } from '$uix/eidos/components/pagination';
//
// <Pagination count={120} let:pages>
// <Pagination.PrevTrigger>Previous</Pagination.PrevTrigger>
// {#each pages as page}
// ...
// {/each}
// <Pagination.NextTrigger>Next</Pagination.NextTrigger>
// </Pagination>
import PaginationRoot from './pagination.svelte';
import PrevTrigger from './pagination-prev-trigger.svelte';
import NextTrigger from './pagination-next-trigger.svelte';
import Item from './pagination-item.svelte';
import Ellipsis from './pagination-ellipsis.svelte';
type PaginationNamespace = typeof PaginationRoot & {
PrevTrigger: typeof PrevTrigger;
NextTrigger: typeof NextTrigger;
Item: typeof Item;
Ellipsis: typeof Ellipsis;
};
const Pagination = PaginationRoot as PaginationNamespace;
Pagination.PrevTrigger = PrevTrigger;
Pagination.NextTrigger = NextTrigger;
Pagination.Item = Item;
Pagination.Ellipsis = Ellipsis;
export { Pagination };
export default Pagination;
export type {
EllipsisItem,
PageItem,
PageRange,
PaginationProps,
PaginationPrevTriggerProps as PrevTriggerProps,
PaginationNextTriggerProps as NextTriggerProps,
PaginationItemProps as ItemProps,
PaginationEllipsisProps as EllipsisProps,
PaginationSize
} from './types';

@ -0,0 +1,10 @@
<script lang="ts">
import { Ellipsis as SomaPaginationEllipsis } from '$soma/components/pagination';
import type { PaginationEllipsisProps } from './types';
let { children, ...rest }: PaginationEllipsisProps = $props();
</script>
<SomaPaginationEllipsis {...rest}>
{@render children?.()}
</SomaPaginationEllipsis>

@ -0,0 +1,10 @@
<script lang="ts">
import { Item as SomaPaginationItem } from '$soma/components/pagination';
import type { PaginationItemProps } from './types';
let { children, ...rest }: PaginationItemProps = $props();
</script>
<SomaPaginationItem {...rest}>
{@render children?.()}
</SomaPaginationItem>

@ -0,0 +1,10 @@
<script lang="ts">
import { NextTrigger as SomaPaginationNextTrigger } from '$soma/components/pagination';
import type { PaginationNextTriggerProps } from './types';
let { children, ...rest }: PaginationNextTriggerProps = $props();
</script>
<SomaPaginationNextTrigger {...rest}>
{@render children?.()}
</SomaPaginationNextTrigger>

@ -0,0 +1,10 @@
<script lang="ts">
import { PrevTrigger as SomaPaginationPrevTrigger } from '$soma/components/pagination';
import type { PaginationPrevTriggerProps } from './types';
let { children, ...rest }: PaginationPrevTriggerProps = $props();
</script>
<SomaPaginationPrevTrigger {...rest}>
{@render children?.()}
</SomaPaginationPrevTrigger>

@ -0,0 +1,105 @@
[data-pagination] {
--_pagination-control-size: var(--pagination-control-size-md);
--_pagination-control-px: var(--pagination-control-px-md);
--_pagination-font-size: var(--pagination-font-size-md);
--_pagination-gap: var(--pagination-gap-md);
display: flex;
align-items: center;
gap: var(--_pagination-gap);
font-family: var(--pagination-font-family);
font-size: var(--_pagination-font-size);
line-height: var(--pagination-line-height);
}
[data-pagination][data-size='sm'] {
--_pagination-control-size: var(--pagination-control-size-sm);
--_pagination-control-px: var(--pagination-control-px-sm);
--_pagination-font-size: var(--pagination-font-size-sm);
--_pagination-gap: var(--pagination-gap-sm);
}
[data-pagination][data-size='lg'] {
--_pagination-control-size: var(--pagination-control-size-lg);
--_pagination-control-px: var(--pagination-control-px-lg);
--_pagination-font-size: var(--pagination-font-size-lg);
--_pagination-gap: var(--pagination-gap-lg);
}
[data-pagination][data-disabled] {
opacity: var(--pagination-disabled-opacity);
}
[data-pagination-prev-trigger],
[data-pagination-next-trigger],
[data-pagination-item] {
display: inline-flex;
align-items: center;
justify-content: center;
min-inline-size: var(--_pagination-control-size);
block-size: var(--_pagination-control-size);
padding-inline: var(--_pagination-control-px);
border: var(--pagination-control-border-width) solid var(--pagination-control-border);
border-radius: var(--pagination-control-radius);
background: var(--pagination-control-bg);
color: var(--pagination-control-color);
font: inherit;
cursor: pointer;
outline: none;
transition:
background var(--pagination-transition-duration) var(--pagination-transition-ease),
border-color var(--pagination-transition-duration) var(--pagination-transition-ease),
color var(--pagination-transition-duration) var(--pagination-transition-ease),
box-shadow var(--pagination-transition-duration) var(--pagination-transition-ease);
}
[data-pagination-prev-trigger]:hover:not([data-disabled]),
[data-pagination-next-trigger]:hover:not([data-disabled]),
[data-pagination-item]:hover:not([data-disabled]) {
border-color: var(--pagination-control-border-hover);
background: var(--pagination-control-bg-hover);
color: var(--pagination-control-color-hover);
}
[data-pagination-prev-trigger]:focus-visible,
[data-pagination-next-trigger]:focus-visible,
[data-pagination-item]:focus-visible {
box-shadow: var(--pagination-focus-shadow);
}
[data-pagination-prev-trigger][data-disabled],
[data-pagination-next-trigger][data-disabled],
[data-pagination-item][data-disabled] {
cursor: not-allowed;
opacity: var(--pagination-disabled-opacity);
}
[data-pagination-item][data-selected] {
border-color: var(--pagination-selected-border);
background: var(--pagination-selected-bg);
color: var(--pagination-selected-color);
}
[data-pagination-item][data-selected]:hover {
border-color: var(--pagination-selected-border-hover);
background: var(--pagination-selected-bg-hover);
color: var(--pagination-selected-color);
}
[data-pagination-ellipsis] {
display: inline-flex;
align-items: center;
justify-content: center;
min-inline-size: var(--_pagination-control-size);
block-size: var(--_pagination-control-size);
color: var(--pagination-ellipsis-color);
user-select: none;
}
@media (prefers-reduced-motion: reduce) {
[data-pagination-prev-trigger],
[data-pagination-next-trigger],
[data-pagination-item] {
transition: none;
}
}

@ -0,0 +1,20 @@
<script lang="ts">
/**
* Eidos `<Pagination>` — visual wrapper over Soma's pagination primitive.
* Soma owns page range calculation and navigation; Eidos adds `size`.
*/
import { ActiveEidos } from '$uix/eidos';
import { Provider as SomaPaginationProvider } from '$soma/components/pagination';
import type { PaginationProps } from './types';
let { size = 'md', page = $bindable(1), children, ...rest }: PaginationProps = $props();
const eidos = ActiveEidos.require();
const resolvedSize = $derived(eidos.resolve(size, 'md'));
</script>
<SomaPaginationProvider {...rest} bind:page data-size={resolvedSize}>
{#snippet children(snippetProps)}
{@render children?.(snippetProps)}
{/snippet}
</SomaPaginationProvider>

@ -0,0 +1,30 @@
import type {
EllipsisItem,
EllipsisProps as SomaPaginationEllipsisProps,
ItemProps as SomaPaginationItemProps,
NextTriggerProps as SomaPaginationNextTriggerProps,
PageItem,
PageRange,
PrevTriggerProps as SomaPaginationPrevTriggerProps,
ProviderProps as SomaPaginationProviderProps
} from '$soma/components/pagination';
import type { ResponsiveProp, Size } from '$uix/eidos/lib/types';
export type PaginationSize = Extract<Size, 'sm' | 'md' | 'lg'>;
/**
* Props for the eidos `<Pagination>`.
*
* Extends Soma's pagination with visual sizing only. Page calculation,
* selected state and disabled state are owned by Soma.
*/
export type PaginationProps = SomaPaginationProviderProps & {
/** Visual size. Accepts a responsive map resolved by ActiveEidos. @default 'md' */
size?: ResponsiveProp<PaginationSize>;
};
export type PaginationPrevTriggerProps = SomaPaginationPrevTriggerProps;
export type PaginationNextTriggerProps = SomaPaginationNextTriggerProps;
export type PaginationItemProps = SomaPaginationItemProps;
export type PaginationEllipsisProps = SomaPaginationEllipsisProps;
export type { EllipsisItem, PageItem, PageRange };

@ -602,6 +602,38 @@
--meter-indicator-bg-above: var(--color-threat-solid); --meter-indicator-bg-above: var(--color-threat-solid);
--meter-transition-duration: var(--duration-normal); --meter-transition-duration: var(--duration-normal);
--meter-transition-ease: var(--ease-default); --meter-transition-ease: var(--ease-default);
--pagination-gap-sm: var(--space-1);
--pagination-gap-md: var(--space-1-5);
--pagination-gap-lg: var(--space-2);
--pagination-control-size-sm: var(--control-height-xs);
--pagination-control-size-md: var(--control-height-sm);
--pagination-control-size-lg: var(--control-height-md);
--pagination-control-px-sm: var(--space-2);
--pagination-control-px-md: var(--space-2-5);
--pagination-control-px-lg: var(--space-3);
--pagination-font-family: var(--font-ui);
--pagination-font-size-sm: var(--font-size-xs);
--pagination-font-size-md: var(--font-size-sm);
--pagination-font-size-lg: var(--font-size-sm);
--pagination-line-height: var(--leading-ui);
--pagination-control-radius: var(--radius-md);
--pagination-control-border-width: var(--border-width);
--pagination-control-border: transparent;
--pagination-control-border-hover: var(--color-border-subtle);
--pagination-control-bg: transparent;
--pagination-control-bg-hover: var(--color-surface-raised);
--pagination-control-color: var(--color-content-secondary);
--pagination-control-color-hover: var(--color-content-primary);
--pagination-selected-border: var(--color-primary-border);
--pagination-selected-border-hover: var(--color-primary-border);
--pagination-selected-bg: var(--color-primary-solid);
--pagination-selected-bg-hover: var(--color-primary-solid-hover);
--pagination-selected-color: var(--color-content-on-solid);
--pagination-ellipsis-color: var(--color-content-muted);
--pagination-focus-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);
--pagination-transition-duration: var(--duration-fast);
--pagination-transition-ease: var(--ease-default);
--pagination-disabled-opacity: 0.56;
--popover-content-z: 75; --popover-content-z: 75;
--popover-overlay-z: 60; --popover-overlay-z: 60;
--popover-overlay-bg: var(--color-overlay); --popover-overlay-bg: var(--color-overlay);

@ -69,6 +69,7 @@
@import './components/tooltip/tooltip.css'; @import './components/tooltip/tooltip.css';
@import './components/progress/progress.css'; @import './components/progress/progress.css';
@import './components/meter/meter.css'; @import './components/meter/meter.css';
@import './components/pagination/pagination.css';
@import './components/toggle/toggle.css'; @import './components/toggle/toggle.css';
@import './components/switch/switch.css'; @import './components/switch/switch.css';
@import './components/collapsible/collapsible.css'; @import './components/collapsible/collapsible.css';

@ -347,6 +347,41 @@ export const THEME_BASE_RECIPE_TOKENS = {
'transition-duration': 'var(--duration-normal)', 'transition-duration': 'var(--duration-normal)',
'transition-ease': 'var(--ease-default)' 'transition-ease': 'var(--ease-default)'
}, },
pagination: {
'gap-sm': 'var(--space-1)',
'gap-md': 'var(--space-1-5)',
'gap-lg': 'var(--space-2)',
'control-size-sm': 'var(--control-height-xs)',
'control-size-md': 'var(--control-height-sm)',
'control-size-lg': 'var(--control-height-md)',
'control-px-sm': 'var(--space-2)',
'control-px-md': 'var(--space-2-5)',
'control-px-lg': 'var(--space-3)',
'font-family': 'var(--font-ui)',
'font-size-sm': 'var(--font-size-xs)',
'font-size-md': 'var(--font-size-sm)',
'font-size-lg': 'var(--font-size-sm)',
'line-height': 'var(--leading-ui)',
'control-radius': 'var(--radius-md)',
'control-border-width': 'var(--border-width)',
'control-border': 'transparent',
'control-border-hover': 'var(--color-border-subtle)',
'control-bg': 'transparent',
'control-bg-hover': 'var(--color-surface-raised)',
'control-color': 'var(--color-content-secondary)',
'control-color-hover': 'var(--color-content-primary)',
'selected-border': 'var(--color-primary-border)',
'selected-border-hover': 'var(--color-primary-border)',
'selected-bg': 'var(--color-primary-solid)',
'selected-bg-hover': 'var(--color-primary-solid-hover)',
'selected-color': 'var(--color-content-on-solid)',
'ellipsis-color': 'var(--color-content-muted)',
'focus-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)',
'transition-duration': 'var(--duration-fast)',
'transition-ease': 'var(--ease-default)',
'disabled-opacity': '0.56'
},
popover: { popover: {
'content-z': '75', 'content-z': '75',
'overlay-z': '60', 'overlay-z': '60',

Loading…
Cancel
Save

Powered by TurnKey Linux.