Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions semcore/wizard/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

CHANGELOG.md standards are inspired by [keepachangelog.com](https://keepachangelog.com/en/1.0.0/).

## [16.2.0] - 2025-10-17

### Changed

- Improve types, added `WizardContentProps` with `noSidebar` property, component rewritten in TypeScript.

## [16.1.11] - 2025-10-24

### Changed
Expand Down
593 changes: 297 additions & 296 deletions semcore/wizard/__tests__/wizard.browser-test.tsx

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion semcore/wizard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"author": "UI-kit team <[email protected]>",
"license": "MIT",
"scripts": {
"build": "pnpm semcore-builder --source=js && pnpm vite build"
"build": "pnpm semcore-builder && pnpm vite build"
},
"exports": {
"require": "./lib/cjs/index.js",
Expand Down
138 changes: 89 additions & 49 deletions semcore/wizard/src/Wizard.jsx → semcore/wizard/src/Wizard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Button from '@semcore/button';
import { createComponent, Component, Root, sstyled } from '@semcore/core';
import type { IRootComponentProps, Intergalactic } from '@semcore/core';
import i18nEnhance from '@semcore/core/lib/utils/enhances/i18nEnhance';
import keyboardFocusEnhance from '@semcore/core/lib/utils/enhances/keyboardFocusEnhance';
import findComponent from '@semcore/core/lib/utils/findComponent';
Expand All @@ -14,46 +15,80 @@ import React from 'react';

import style from './style/wizard.shadow.css';
import { localizedMessages } from './translations/__intergalactic-dynamic-locales';
import type {
WizardStep,
WizardProps,
WizardStepProps,
WizardStepperProps,
WizardSidebarProps,
WizardContentProps,
IntergalacticWizardStepperComponent,
WizardType,
WizardStepBackProps,
WizardStepNextProps,
} from './Wizard.types';

class WizardRoot extends Component {
class WizardRoot extends Component<WizardProps, {}, {}, typeof WizardRoot.enhance> {
static displayName = 'Wizard';
static style = style;
static enhance = [i18nEnhance(localizedMessages), uniqueIDEnhancement()];
static enhance = [i18nEnhance(localizedMessages), uniqueIDEnhancement()] as const;
static defaultProps = {
step: null,
i18n: localizedMessages,
locale: 'en',
};

_steps = new Map();
modalRef = React.createRef();
contentRef = React.createRef();
modalRef = React.createRef<HTMLElement>();
contentRef = React.createRef<HTMLElement>();
state = { highlighted: null };

getStepProps(props) {
stepperRefs: Array<HTMLElement | null> = [];

getStepId(step: WizardStep): string {
return `${this.asProps.uid}-step-${step}`;
}

getId(): string {
return `${this.asProps.uid}-title`;
}

getStepperId(step: WizardStep): string {
const { uid } = this.asProps;

return `${uid}-stepper-${step}`;
}

getContentId(step: WizardStep): string {
const { uid } = this.asProps;

return `${uid}-content-${step}`;
}

getStepProps(props: WizardStepProps) {
return {
steps: this._steps,
active: props.step === this.asProps.step,
id: `${this.asProps.uid}-step-${props.step}`,
id: this.getStepId(props.step),
};
}

getSidebarProps() {
return {
uid: this.asProps.uid,
id: this.getId(),
};
}

getContentProps() {
const { Children } = this.asProps;
const { Children, step } = this.asProps;

const Sidebar = findComponent(Children, ['Wizard.Sidebar']);
const Sidebar = findComponent(Children, ['Wizard.Sidebar'], true);

return {
uid: this.asProps.uid,
step: this.asProps.step,
ref: this.contentRef,
noSidebar: !Sidebar,
['aria-labelledby']: this.getStepperId(step),
id: this.getContentId(step),
};
}

Expand All @@ -71,8 +106,7 @@ class WizardRoot extends Component {
};
}

stepperRefs = [];
stepperFocusPrev = (i) => () => {
stepperFocusPrev = (i: number) => () => {
const prevStep = this._steps.get(i);
if (!prevStep) return;
this.setState({ highlighted: prevStep?.step });
Expand All @@ -81,7 +115,7 @@ class WizardRoot extends Component {
}, 0);
};

stepperFocusNext = (i) => () => {
stepperFocusNext = (i: number) => () => {
const nextStep = this._steps.get(i + 2);
if (!nextStep) return;
this.setState({ highlighted: nextStep?.step });
Expand All @@ -90,7 +124,7 @@ class WizardRoot extends Component {
}, 0);
};

getStepperProps(props, i) {
getStepperProps(props: WizardStepperProps, i: number) {
let number = i + 1;
if (this._steps.has(props.step)) {
const step = this._steps.get(props.step);
Expand All @@ -101,32 +135,40 @@ class WizardRoot extends Component {
const active = props.step === this.asProps.step;
const highlighted =
this.state.highlighted === props.step || (this.state.highlighted === null && i === 0);

return {
active,
tabIndex: highlighted ? 0 : -1,
'tabIndex': highlighted ? 0 : -1,
number,
getI18nText: this.asProps.getI18nText,
uid: this.asProps.uid,
ref: (node) => {
'getI18nText': this.asProps.getI18nText,
'uid': this.asProps.uid,
'ref': (node: HTMLElement | null) => {
this.stepperRefs[i] = node;
},
focusNext: this.stepperFocusNext(i),
focusPrev: this.stepperFocusPrev(i),
'focusNext': this.stepperFocusNext(i),
'focusPrev': this.stepperFocusPrev(i),

'id': this.getStepperId(props.step),
'aria-controls': active ? this.getContentId(props.step) : undefined,
'aria-disabled': props.disabled,
'aria-selected': active,
};
}

componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: WizardProps) {
if (prevProps.step === this.asProps.step) return;
this.setState({ highlighted: this.asProps.step || null });
setTimeout(() => {
if (prevProps.step === this.asProps.step) return;
setFocus(this.contentRef.current);
if (this.contentRef.current) {
setFocus(this.contentRef.current);
}
}, 1);
}

render() {
const SWizard = this.Root;
const { Children, styles, uid } = this.asProps;
const { Children, styles } = this.asProps;

this._steps.clear();

Expand All @@ -135,31 +177,31 @@ class WizardRoot extends Component {
render={Modal}
aria-label={undefined}
ref={this.modalRef}
aria-labelledby={`${uid}-title`}
aria-labelledby={this.getId()}
>
<Children />
</SWizard>,
);
}
}

function Sidebar(props) {
const { Children, styles, title, uid } = props;
function Sidebar(props: WizardSidebarProps & IRootComponentProps) {
const { Children, styles, title, id } = props;
const SSidebar = Root;
const SSidebarHeader = 'h2';
const SSidebarMenu = 'div';

return sstyled(styles)(
<SSidebar render={Box} __excludeProps={['title']}>
{title && <SSidebarHeader id={`${uid}-title`}>{title}</SSidebarHeader>}
<SSidebar render={Box} __excludeProps={['title', 'id']}>
{title && <SSidebarHeader id={id}>{title}</SSidebarHeader>}
<SSidebarMenu role='tablist' aria-orientation='vertical'>
<Children />
</SSidebarMenu>
</SSidebar>,
);
}

function Step(props) {
function Step(props: IRootComponentProps & WizardStepProps) {
const SStep = Root;
const { Children, styles, active } = props;
if (active) {
Expand All @@ -172,18 +214,15 @@ function Step(props) {
return null;
}

function Stepper(props) {
function Stepper(props: Required<WizardStepperProps> & IRootComponentProps) {
const {
Children,
styles,
step,
active,
onActive,
completed,
disabled,
number,
getI18nText,
uid,
focusNext,
focusPrev,
} = props;
Expand All @@ -193,14 +232,14 @@ function Stepper(props) {
const SCompleted = CheckM;

const handlerClick = React.useCallback(
(e) => {
(e: React.SyntheticEvent<HTMLElement>) => {
if (onActive) onActive(step, e);
},
[step, onActive],
);

const handlerKeyDown = React.useCallback(
(e) => {
(e: React.KeyboardEvent) => {
if (onActive && (e.key === 'Enter' || e.key === ' ')) {
e.preventDefault();
onActive(step, e);
Expand All @@ -221,10 +260,6 @@ function Stepper(props) {
<SStepper
render={Box}
role='tab'
id={`${uid}-stepper-${step}`}
aria-controls={active ? `${uid}-content-${step}` : undefined}
aria-disabled={disabled}
aria-selected={active}
onClick={handlerClick}
onKeyDown={handlerKeyDown}
>
Expand All @@ -239,22 +274,20 @@ function Stepper(props) {

Stepper.enhance = [keyboardFocusEnhance()];

function Content(props) {
const { Children, children: hasChildren, styles, uid, step } = props;
function Content(props: WizardContentProps & IRootComponentProps) {
const { Children, styles } = props;
const SContent = Root;
return sstyled(styles)(
<SContent
render={Box}
role='tabpanel'
aria-labelledby={`${uid}-stepper-${step}`}
id={`${uid}-content-${step}`}
>
{hasChildren ? <Children /> : stepName}
<Children />
</SContent>,
);
}

function StepBack(props) {
function StepBack(props: Required<WizardStepBackProps> & IRootComponentProps) {
const SStepBack = Root;
const { Children, children: hasChildren, styles, getI18nText, stepName } = props;
const handleClick = React.useCallback(() => {
Expand All @@ -275,7 +308,7 @@ function StepBack(props) {
</SStepBack>,
);
}
function StepNext(props) {
function StepNext(props: Required<WizardStepNextProps> & IRootComponentProps) {
const SStepNext = Root;
const { Children, children: hasChildren, styles, getI18nText, stepName } = props;
const handleClick = React.useCallback(() => {
Expand Down Expand Up @@ -304,8 +337,15 @@ const Wizard = createComponent(WizardRoot, {
Stepper,
StepBack,
StepNext,
});
}) as WizardType;

export const wrapWizardStepper = (wrapper) => wrapper;
export const wrapWizardStepper = <PropsExtending extends {}>(
wrapper: (
props: Intergalactic.InternalTypings.UntypeRefAndTag<
Intergalactic.InternalTypings.ComponentPropsNesting<IntergalacticWizardStepperComponent>
> &
PropsExtending,
) => React.ReactNode,
) => wrapper as IntergalacticWizardStepperComponent<PropsExtending>;

export default Wizard;
Loading
Loading