Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lukso/web-components",
"version": "1.188.0",
"version": "1.190.0",
"type": "module",
"files": [
"dist",
Expand Down
105 changes: 97 additions & 8 deletions src/components/lukso-wizard/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { html } from 'lit'
import { html, nothing } from 'lit'
import { property } from 'lit/decorators.js'
import { repeat } from 'lit/directives/repeat.js'
import { tv } from 'tailwind-variants'
Expand All @@ -12,6 +12,8 @@ export type WizardStep = {
}

export type WizardSize = 'small' | 'medium' | 'large' | 'full-width'
export type WizardVariant = 'default' | 'numbered'
export type WizardLinkableMode = 'previous' | 'all'

/**
* A multi-step progress indicator (stepper) showing labelled steps with completed/current/upcoming states.
Expand All @@ -27,6 +29,40 @@ export class LuksoWizard extends TailwindStyledElement(style) {
@property({ type: String })
size: WizardSize = 'medium'

@property({ type: String })
variant: WizardVariant = 'default'

@property({ type: Boolean, attribute: 'is-linkable' })
isLinkable = false

@property({ type: String, attribute: 'linkable-mode' })
linkableMode: WizardLinkableMode = 'previous'

private numberedStepStyles = tv({
slots: {
base: 'flex items-center flex-1 last:flex-none',
circle: `lukso-wizard-numbered-circle w-7 h-7 rounded-full flex items-center justify-center
shrink-0 body-inter-12-bold text-neutral-80 border border-neutral-80`,
label: 'ml-2 body-inter-12-medium text-neutral-80 whitespace-nowrap',
line: 'lukso-wizard-numbered-line flex-1 h-[2px] mx-3 bg-neutral-90 transition-colors duration-300',
},
variants: {
completed: {
true: {
circle: 'bg-green-45 text-neutral-100 border border-green-54',
label: 'text-green-54',
line: 'bg-green-45',
},
},
active: {
true: {
circle: 'bg-neutral-10 text-neutral-100 border border-neutral-20 ',
label: 'text-neutral-20',
},
},
},
})

private stepStyles = tv({
slots: {
base: `inline-flex flex-col items-center justify-end first:-ml-12 last:-mr-12 relative
Expand Down Expand Up @@ -79,14 +115,54 @@ export class LuksoWizard extends TailwindStyledElement(style) {
},
})

stepTemplate(step: WizardStep, index: number) {
private handleStepClick(stepNumber: number) {
this.dispatchEvent(
new CustomEvent('on-step-click', {
detail: { step: stepNumber },
bubbles: true,
composed: true,
})
)
}

numberedStepTemplate(step: WizardStep, index: number, totalSteps: number) {
const isCompleted = index + 1 < this.activeStep
const isActive = index + 1 === this.activeStep
const isLinkableStep =
this.isLinkable &&
!isActive &&
(isCompleted || this.linkableMode === 'all')
const { base, circle, label, line } = this.numberedStepStyles({
completed: isCompleted,
active: isActive,
})
return html`<li
class="${base()} ${isLinkableStep ? 'cursor-pointer' : ''}"
@click=${isLinkableStep ? () => this.handleStepClick(index + 1) : nothing}
>
Comment thread
federico-freddi marked this conversation as resolved.
Comment thread
federico-freddi marked this conversation as resolved.
<div class="${circle()}">${index + 1}</div>
<span class="${label()}">${step.label}</span>
${index < totalSteps - 1 ? html`<div class="${line()}"></div>` : nothing}
</li>`
}

defaultStepTemplate(step: WizardStep, index: number) {
const isCompleted = index + 1 < this.activeStep
const isActive = index + 1 === this.activeStep
const isLinkableStep =
this.isLinkable &&
!isActive &&
(isCompleted || this.linkableMode === 'all')
const { base, circle, innerCircle } = this.stepStyles({
completed: index + 1 < this.activeStep,
completed: isCompleted,
active: index + 1 === this.activeStep,
current: index === this.activeStep - 2,
size: this.size,
})
return html`<li class="${base()}">
return html`<li
class="${base()} ${isLinkableStep ? 'cursor-pointer' : ''}"
@click=${isLinkableStep ? () => this.handleStepClick(index + 1) : nothing}
>
Comment thread
federico-freddi marked this conversation as resolved.
<div
class="text-purple-51 nav-inter-8-medium-uppercase whitespace-pre-line flex text-center break-words uppercase leading-none"
>
Expand All @@ -101,12 +177,25 @@ export class LuksoWizard extends TailwindStyledElement(style) {
render() {
const steps = JSON.parse(this.steps) as WizardStep[]

Comment thread
federico-freddi marked this conversation as resolved.
if (this.variant === 'numbered') {
return html`
<ul class="flex items-center w-full" data-testid="wizard">
${repeat(
steps || [],
(_, index) => index,
(step, index) =>
this.numberedStepTemplate(step, index, steps.length)
Comment thread
federico-freddi marked this conversation as resolved.
)}
</ul>
`
}

return html`
<ul class="flex justify-center" data-testid="wizard">
${repeat(
steps || [],
step => steps.indexOf(step),
(step, index) => this.stepTemplate(step, index)
(_, index) => index,
(step, index) => this.defaultStepTemplate(step, index)
)}
</ul>
`
Expand All @@ -115,8 +204,8 @@ export class LuksoWizard extends TailwindStyledElement(style) {
updated() {
// delay animation to allow for DOM to be updated
setTimeout(() => {
const currentStep = this.shadowRoot.querySelectorAll('.current')
currentStep[0]?.classList.add('animated-step')
const currentStep = this.shadowRoot?.querySelectorAll('.current')
currentStep?.[0]?.classList.add('animated-step')
}, 10)
}
}
Expand Down
118 changes: 117 additions & 1 deletion src/components/lukso-wizard/lukso-wizard.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,36 @@ const meta: Meta = {
category: 'Attributes',
},
},
variant: {
control: { type: 'select' },
options: ['default', 'numbered'],
table: {
category: 'Attributes',
},
},
isLinkable: {
name: 'is-linkable',
control: { type: 'boolean' },
table: {
category: 'Attributes',
},
},
linkableMode: {
name: 'linkable-mode',
control: { type: 'select' },
options: ['previous', 'all'],
table: {
category: 'Attributes',
},
},
'on-step-click': {
description:
'Fired when a linkable step is clicked (requires `is-linkable`). The `detail.step` property contains the 1-based index of the clicked step.',
table: {
category: 'Events',
type: { summary: 'CustomEvent<{ step: number }>' },
},
},
'active-step': {
name: 'activeStep',
},
Expand All @@ -53,6 +83,8 @@ LYXe`,
],
size: 'medium',
activeStep: 3,
isLinkable: false,
linkableMode: 'previous',
},
parameters: {
controls: {
Expand All @@ -68,11 +100,95 @@ LYXe`,

export default meta

const Template = ({ steps, activeStep, size }) =>
const Template = ({
steps,
activeStep,
size,
variant,
isLinkable,
linkableMode,
}: {
steps: object[]
activeStep: number
size: string
variant: string
isLinkable: boolean
linkableMode: string
}) =>
html`<lukso-wizard
steps=${JSON.stringify(steps)}
active-step=${activeStep}
size=${size ? size : nothing}
variant=${variant ? variant : nothing}
?is-linkable=${isLinkable}
linkable-mode=${linkableMode ?? nothing}
></lukso-wizard>`

export const BasicWizard = Template.bind({})

export const NumberedWizard = Template.bind({})
;(NumberedWizard as typeof Template & { args: object }).args = {
steps: [
{ label: 'Token information' },
{ label: 'Token settings' },
{ label: 'Review' },
{ label: 'Deploy' },
],
size: 'full-width',
activeStep: 3,
variant: 'numbered',
}

const linkableSteps = [
{ label: 'Token information' },
{ label: 'Token settings' },
{ label: 'Review' },
{ label: 'Deploy' },
]

function buildLinkableStory(linkableMode: 'previous' | 'all') {
return () => {
let activeStep = 3

const container = document.createElement('div')
container.style.cssText =
'display:flex;flex-direction:column;gap:16px;width:100%'

const info = document.createElement('p')
info.style.cssText = 'margin:0;font-size:13px;color:#666'

const updateInfo = () => {
info.textContent =
linkableMode === 'all'
? `Active step: ${activeStep} — click any step (except active) to jump to it`
: `Active step: ${activeStep} — click a completed step to navigate back`
}
updateInfo()

const wizard = document.createElement('lukso-wizard')
wizard.setAttribute('steps', JSON.stringify(linkableSteps))
wizard.setAttribute('active-step', String(activeStep))
wizard.setAttribute('size', 'full-width')
wizard.setAttribute('variant', 'numbered')
wizard.setAttribute('is-linkable', '')
wizard.setAttribute('linkable-mode', linkableMode)

wizard.addEventListener('on-step-click', (e: Event) => {
activeStep = (e as CustomEvent<{ step: number }>).detail.step
wizard.setAttribute('active-step', String(activeStep))
updateInfo()
})

container.appendChild(info)
container.appendChild(wizard)
return container
}
}

export const LinkableWizard = Object.assign(buildLinkableStory('previous'), {
storyName: 'Linkable Wizard (previous only)',
})

export const LinkableWizardAll = Object.assign(buildLinkableStory('all'), {
storyName: 'Linkable Wizard (all steps)',
})
Loading