Skip to content

Commit

Permalink
New Steps and Timeline components (#395)
Browse files Browse the repository at this point in the history
* Fix ButtonGroup api reference

* New Timeline component (replaces Steps) (WIP)

* Add compact support

* Simplify grid cols/rows setup

* Support `complete` status

* Add timeline context and use to set vertical, compact, icon, and snapIcon within TimelineItem

* Support customizing completed color

* Remove old Steps component (replaced by Timeline).  Add changeset

* Round edges of timeline

* Support providing custom point (not just icon)

* Remove extra changeset

* Add new Steps component

* Update changesets

* Update changeset
  • Loading branch information
techniq committed Jun 18, 2024
1 parent 9de6dbf commit d1d91fc
Show file tree
Hide file tree
Showing 13 changed files with 866 additions and 97 deletions.
5 changes: 5 additions & 0 deletions .changeset/shy-cobras-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte-ux': minor
---

Add new `Steps` component
5 changes: 5 additions & 0 deletions .changeset/witty-pigs-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte-ux': minor
---

Add new `Timeline` component (replaces old `Steps` component usage). See also new `Steps` component
73 changes: 73 additions & 0 deletions packages/svelte-ux/src/lib/components/Step.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script lang="ts">
import type { ComponentProps } from 'svelte';
import Icon from './Icon.svelte';
import { getSteps } from './Steps.svelte';
import { getComponentClasses } from './theme.js';
import { cls } from '../utils/styles.js';
/** Override content (by default uses an incrementing counter)*/
export let content: string | undefined = undefined;
/** Use icon instead of content */
export let icon: ComponentProps<Icon>['data'] = undefined;
/** If completed, will color content and line leading up to item */
export let completed = false;
export let classes: {
root?: string;
line?: string;
content?: string;
/** Apply classes to completed item point and line leading up to item */
completed?: string;
} = {};
const settingsClasses = getComponentClasses('Step');
const stepsContext = getSteps();
$: vertical = stepsContext?.vertical ?? false;
</script>

<li
class={cls(
'Step',
'group grid place-items-center text-center',
vertical
? 'grid-cols-[40px_1fr] gap-2 min-h-16 justify-items-start'
: 'grid-rows-[40px_1fr] min-w-16',
settingsClasses.root,
classes.root,
$$props.class
)}
>
<div
class={cls(
'group-first:hidden col-start-1 row-start-1 bg-surface-300 text-surface-content top-0',
vertical ? 'h-full w-2 -mt-[100%] justify-self-center' : 'h-2 w-full -ml-[100%]',
completed && (settingsClasses.completed ?? classes.completed ?? 'bg-primary'),
settingsClasses.line,
classes.line
)}
/>

<slot />

<div
class={cls(
'bg-surface-300 text-surface-content relative col-start-1 row-start-1 grid size-8 place-items-center place-self-center rounded-full [counter-increment:step]',
content == null && !$$slots.content && icon == null && 'before:content-[counter(step)]',
completed &&
(settingsClasses.completed ?? classes.completed ?? 'bg-primary text-primary-content'),
settingsClasses.content,
classes.content
)}
>
<slot name="content">
{#if icon}
<Icon data={icon} />
{:else}
{content ?? ''}
{/if}
</slot>
</div>
</li>
85 changes: 59 additions & 26 deletions packages/svelte-ux/src/lib/components/Steps.svelte
Original file line number Diff line number Diff line change
@@ -1,32 +1,65 @@
<script lang="ts" context="module">
import { type ComponentProps, setContext, getContext } from 'svelte';
type StepsContext = {
vertical: boolean;
};
const stepsKey = Symbol();
export function setSteps(value: StepsContext | undefined) {
setContext(stepsKey, value);
}
export function getSteps() {
return getContext<StepsContext | undefined>(stepsKey);
}
</script>

<script lang="ts">
type T = $$Generic;
import Step from './Step.svelte';
import { cls } from '../utils/styles.js';
import { getComponentClasses } from './theme.js';
import Icon from './Icon.svelte';
type StepData = {
label: string;
content?: string;
icon?: ComponentProps<Icon>['data'];
};
export let data: StepData[] = [];
/** Align vertically (default: horizontal) */
export let vertical: boolean = false;
export let data: T[];
export let lineGap = 4;
export let classes: {
root?: string;
item?: ComponentProps<Step>['classes'];
} = {};
const settingsClasses = getComponentClasses('Steps');
// binded
let circleSize = 0;
setSteps({
vertical,
});
</script>

<ol
style:--circleSize={circleSize}
style:--lineTop="{circleSize + lineGap}px"
style:--lineBottom="{lineGap}px"
style:--lineOffset="{circleSize / 2}px"
<ul
class={cls(
'Steps',
'inline-grid grid-flow-col overflow-hidden overflow-x-auto auto-cols-fr [counter-reset:step]',
vertical ? 'grid-flow-row' : 'grid-flow-col',
settingsClasses.root,
classes.root,
$$props.class
)}
>
{#each data as item, index}
<li class="step relative flex gap-4 pb-10">
<div bind:clientWidth={circleSize}>
<slot name="marker" {item} />
</div>

{#if index < data.length - 1}
<div
class="line absolute top-[var(--lineTop)] bottom-[var(--lineBottom)] left-0 w-[2px] translate-x-[var(--lineOffset)] bg-surface-content/20"
/>
{/if}

<slot name="item" {item} />
</li>
{/each}
</ol>
<slot {data}>
{#each data as item}
<Step classes={classes.item} {...item}>
{item.label}
</Step>
{/each}
</slot>
</ul>
79 changes: 79 additions & 0 deletions packages/svelte-ux/src/lib/components/Timeline.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<script lang="ts" context="module">
import { type ComponentProps, setContext, getContext } from 'svelte';
type TimelineContext = {
vertical: boolean;
compact: boolean;
icon: ComponentProps<Icon>['icon'];
snapPoint: boolean;
};
const timelineKey = Symbol();
export function setTimeline(value: TimelineContext | undefined) {
setContext(timelineKey, value);
}
export function getTimeline() {
return getContext<TimelineContext | undefined>(timelineKey);
}
</script>

<script lang="ts">
import TimelineItem from './TimelineItem.svelte';
import { cls } from '../utils/styles.js';
import { getComponentClasses } from './theme.js';
import Icon from './Icon.svelte';
type TimelineItemData = {
start?: string | boolean;
end?: string | boolean;
icon?: ComponentProps<Icon>['data'];
completed?: boolean;
};
export let data: TimelineItemData[] = [];
/** Align vertically (default: horizontal) */
export let vertical: boolean = false;
/** Place timeline on left and all start/end items on end side */
export let compact: boolean = false;
/** Common icon for all items */
export let icon: ComponentProps<TimelineItem>['icon'] = undefined;
/** Snap point to start */
export let snapPoint = false;
export let classes: {
root?: string;
item?: ComponentProps<TimelineItem>['classes'];
} = {};
const settingsClasses = getComponentClasses('Timeline');
setTimeline({
vertical,
compact,
icon,
snapPoint,
});
</script>

<ul
class={cls(
'Timeline',
'relative flex',
vertical && 'flex-col timeline-vertical',
settingsClasses.root,
classes.root,
$$props.class
)}
>
<slot {data}>
{#each data as item}
<TimelineItem classes={classes.item} {...item} />
{/each}
</slot>
</ul>
Loading

0 comments on commit d1d91fc

Please sign in to comment.