From 67dae969069080d37506376d69910a347f7c6d31 Mon Sep 17 00:00:00 2001 From: Philipp Karlsson Date: Tue, 1 Oct 2024 12:49:16 +0200 Subject: [PATCH 1/5] Adding basic TourComponent with wrappers --stable --- library/src/components/tour/TourWrapper.tsx | 167 ++++++++++++++ showcase/public/showcase-sources.txt | 197 +++++++++++++++++ .../showcase/wrapper/TourShowcase.tsx | 203 ++++++++++++++++++ showcase/src/useShowcases.tsx | 2 + 4 files changed, 569 insertions(+) create mode 100644 library/src/components/tour/TourWrapper.tsx create mode 100644 showcase/src/components/showcase/wrapper/TourShowcase.tsx diff --git a/library/src/components/tour/TourWrapper.tsx b/library/src/components/tour/TourWrapper.tsx new file mode 100644 index 00000000..6b8a4b60 --- /dev/null +++ b/library/src/components/tour/TourWrapper.tsx @@ -0,0 +1,167 @@ +import ReactJoyride, { type Step } from "react-joyride" +import React, { useRef, useState } from "react" +import {showErrorFlag, showInformationFlag} from "../ToastFlag"; + +export class TourStep { + step: Step + + constructor() { + this.step = { + content: <>Step should be overwritten., + target: "body", + } + } + + async onInit(next: () => void) { + next() + } + + async onPrepare(next: () => void) { + next() + } + + async onExit() {} +} + +export interface TourProps { + isActive: boolean + setActive: (active: boolean) => void + steps: Array + skipOnError: boolean + showInfoAndError: boolean + beforeAll: () => void + afterAll: () => void +} + +export function Tour({ + isActive, + setActive, + steps, + skipOnError, + showInfoAndError, + beforeAll, + afterAll, +}: TourProps) { + const [stepIndex, setStepIndex] = useState(0) + const isInit = useRef(false) + + function next() { + setStepIndex(stepIndex + 1) + } + + function before() { + setStepIndex(stepIndex - 1) + } + + const _steps = steps.map((it) => it.step) + + return ( + { + const { action, index, lifecycle, type } = joyrideState + + const lpBeforeStep = + stepIndex <= 0 ? undefined : steps[stepIndex - 1] + const lpNextStep = + stepIndex >= 0 && stepIndex < steps.length + ? steps[stepIndex + 1] + : undefined + const lpStep = steps[stepIndex] + + if (type === "error" || type === "error:target_not_found") { + if (skipOnError) { + if (showInfoAndError) { + showInformationFlag({ + title: "Tour-Info", + description: `Ein Step [${lpStep.step?.title ?? "Unbekannt"}] wurde übersprungen.`, + }) + } + next() + } else { + if (showInfoAndError) { + showErrorFlag({ + title: "Tour-Fehler", + description: `Fehler bei Step [${lpStep.step?.title ?? "Unbekannt"}]. Das Element wurde nicht gefunden.`, + }) + } + setStepIndex(0) + isInit.current = false + } + afterAll() + } + + if (action === "start" && lifecycle === "init") { + beforeAll() + isInit.current = false + lpStep?.onInit(() => { + isInit.current = true + }) + } + + if (type === "step:after") { + if (action === "prev") { + lpStep?.onExit() + if (lpBeforeStep) { + lpBeforeStep.onPrepare(before) + } else { + before() + } + } else if (action === "next") { + lpStep?.onExit() + if (lpNextStep) { + lpNextStep.onPrepare(next) + } else { + next() + } + } + } + + if (action === "skip" || action === "close") { + setActive(false) + isInit.current = false + afterAll() + } + + if ( + action === "reset" || + (type === "tour:end" && action === "next") + ) { + setActive(false) + setStepIndex(0) + isInit.current = false + afterAll() + } + }} + steps={_steps} + debug={true} + /> + ) +} diff --git a/showcase/public/showcase-sources.txt b/showcase/public/showcase-sources.txt index 341e1768..37333547 100644 --- a/showcase/public/showcase-sources.txt +++ b/showcase/public/showcase-sources.txt @@ -8460,6 +8460,203 @@ function TooltipShowcase(props: ShowcaseProps) { export default TooltipShowcase +import CrossIcon from "@atlaskit/icon/glyph/cross"; +import {Button, ButtonGroup, Modal, Select} from "@linked-planet/ui-kit-ts"; +import {Tour, TourStep} from "@linked-planet/ui-kit-ts/components/tour/TourWrapper"; +import React, {useState} from "react" +import ReactJoyride, {Step} from "react-joyride"; +import ShowcaseWrapperItem, {type ShowcaseProps,} from "../../ShowCaseWrapperItem/ShowcaseWrapperItem" + +//#region tour +function TourExample() { + const [isActive, setActive] = useState(false) + const [popup, setPopup] = useState(false) + + const defaultLocale = { + back: "Zurück", + close: "Schließen", + last: "Fertig", + next: "Weiter", + open: "Öffnen", + skip: "Überspringen", + } as const + + const InitStep = new (class extends TourStep { + step: Step = { + title: "Tour starten", + target: "#tour-start", + disableBeacon: true, + showSkipButton: false, + placement: "bottom", + locale: defaultLocale, + content: ( + + Mit Klick auf diesen Button können Sie jederzeit die Tour + starten. + + ), + } + })() + + const SecondStep = new (class extends TourStep { + step: Step = { + title: "Button", + target: "*[data-id='Test-1']", + disableBeacon: true, + showSkipButton: false, + placement: "right", + locale: defaultLocale, + content: ( + + Klicken Sie diesen Button um das Popup zu öffnen. + + ), + } + })() + + const PopupStep = new (class extends TourStep { + step: Step = { + title: "Popup", + target: "*[data-id='select']", + disableBeacon: true, + showSkipButton: false, + placement: "right", + locale: defaultLocale, + content: ( + + Hier ist ein Select im Popup, was in der Tour geöffnet wurde. + + ), + } + + async onPrepare(next: () => void): Promise { + setPopup(true) + setTimeout(() => next(), 500) + } + + async onExit(): Promise { + setPopup(false) + } + })() + + const ThirdStep = new (class extends TourStep { + step: Step = { + title: "Weiterer Button", + target: "*[data-id='Test-2']", + disableBeacon: true, + showSkipButton: false, + placement: "right", + locale: defaultLocale, + content: ( + + This step closes the popup and continues with this button. + + ), + } + + async onPrepare(next: () => void): Promise { + setPopup(false) + setTimeout(() => next(), 500) + } + })() + + return ( +
+ +
+ + { + // initialize dummy data or other inits before tour starts + console.info("Starting Tour") + }} + afterAll={() => { + // cleanup dummy data or other inits after tour finished + console.info("Ending Tour") + }} + /> +
+ + +
+ { + if (!opened) setPopup(false) + }} + shouldCloseOnEscapePress={true} + accessibleDialogDescription="This is a modal dialog example" + > + + + Sample Modal + + + + +
+

This is the body of the modal.

+
+ +
+ + + + + +
+
+ ) +} + +//#endregion tour + +export default function TourShowcase(props: ShowcaseProps) { + return ( + , + sourceCodeExampleId: "tour", + }, + ]} + /> + ) +} diff --git a/showcase/src/useShowcases.tsx b/showcase/src/useShowcases.tsx index 1f7c0251..23c6e02a 100644 --- a/showcase/src/useShowcases.tsx +++ b/showcase/src/useShowcases.tsx @@ -50,6 +50,7 @@ import BlanketShowcase from "./components/showcase/wrapper/BlanketShowcase" import BreadcrumbsShowcase from "./components/showcase/wrapper/BreadcrumbsShowcase" import SectionMessageShowcase from "./components/showcase/wrapper/SectionMessageShowcase" import DragAndDropShowcase from "./components/showcase/wrapper/DragAndDropShowcase" +import TourShowcase from "./components/showcase/wrapper/TourShowcase"; export default function useShowcases({ overallSourceCode, }: { @@ -169,6 +170,7 @@ export default function useShowcases({ "Truncated Text": ( ), + Tour: , Utils: , }), [overallSourceCode], From 1d376ad7835ff829a7bb1be716a56981521a2287 Mon Sep 17 00:00:00 2001 From: "Markus T." <19794318+marcus-wishes@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:23:33 +0200 Subject: [PATCH 2/5] Form - changed exports --- library/src/components/EventList.tsx | 12 ++--- .../form/elements/CheckboxFormField.tsx | 5 +- .../form/elements/InputFormField.tsx | 5 +- .../form/elements/SelectMultiFormField.tsx | 4 +- .../form/elements/SelectSingleFormField.tsx | 4 +- library/src/components/form/index.ts | 50 +++++++++++++++++ .../showcase/wrapper/FormShowcase.tsx | 54 +++++++++---------- 7 files changed, 91 insertions(+), 43 deletions(-) create mode 100644 library/src/components/form/index.ts diff --git a/library/src/components/EventList.tsx b/library/src/components/EventList.tsx index ca8249bb..dd4bf664 100644 --- a/library/src/components/EventList.tsx +++ b/library/src/components/EventList.tsx @@ -3,7 +3,7 @@ import type { Dayjs } from "dayjs" import { twMerge } from "tailwind-merge" import type { TimeType } from "../utils" -export interface EventObject { +export interface EventListObject { key: string title?: string subtitle?: string @@ -11,13 +11,13 @@ export interface EventObject { endDate: Dayjs | undefined } -interface EventWrapper { +interface EventWrapper { booking: T renderStartDate: Dayjs renderEndDate: Dayjs } -export interface EventListProps { +export interface EventListProps { items: T[] minStartTime: Dayjs maxEndTime: Dayjs @@ -33,7 +33,7 @@ export interface EventListProps { style?: React.CSSProperties } -function useOrderByDateBookings( +function useOrderByDateBookings( items: T[], _minStartTime: Dayjs, maxEndTime: Dayjs, @@ -122,7 +122,7 @@ const dateFormat = Intl.DateTimeFormat(undefined, { year: "numeric", }) -function defaultRenderEvent( +function defaultRenderEvent( booking: T, startDate: Dayjs | undefined, endDate: Dayjs | undefined, @@ -149,7 +149,7 @@ function defaultRenderEvent( ) } -export function EventList({ +export function EventList({ items, renderEvent = defaultRenderEvent, renderTimeHeader, diff --git a/library/src/components/form/elements/CheckboxFormField.tsx b/library/src/components/form/elements/CheckboxFormField.tsx index fc5ba62b..9889dc0e 100644 --- a/library/src/components/form/elements/CheckboxFormField.tsx +++ b/library/src/components/form/elements/CheckboxFormField.tsx @@ -4,7 +4,8 @@ import type { FormField } from "../DynamicForm" import { Label } from "../../inputs" import { Checkbox } from "../../Checkbox" -export interface CheckboxFormField extends FormField { +export interface CheckboxFormFieldProps + extends FormField { onChange?: (value: string) => void } @@ -15,7 +16,7 @@ export function CheckboxFormField({ required, description, title, -}: CheckboxFormField) { +}: CheckboxFormFieldProps) { const fieldValue = formProps.watch(name) const onChangeCB = useRef(onChange) if (onChangeCB.current !== onChange) { diff --git a/library/src/components/form/elements/InputFormField.tsx b/library/src/components/form/elements/InputFormField.tsx index 01f6263c..7e3a748e 100644 --- a/library/src/components/form/elements/InputFormField.tsx +++ b/library/src/components/form/elements/InputFormField.tsx @@ -3,7 +3,8 @@ import type { FieldValues } from "react-hook-form" import type { FormField } from "../DynamicForm" import { Input, Label } from "../../inputs" -export interface InputFormField extends FormField { +export interface InputFormFieldProps + extends FormField { onChange?: (value: string) => void placeholder?: string } @@ -16,7 +17,7 @@ export function InputFormField({ description, title, placeholder, -}: InputFormField) { +}: InputFormFieldProps) { const fieldValue = formProps.watch(name) const onChangeCB = useRef(onChange) if (onChangeCB.current !== onChange) { diff --git a/library/src/components/form/elements/SelectMultiFormField.tsx b/library/src/components/form/elements/SelectMultiFormField.tsx index 906adbe6..fffcef41 100644 --- a/library/src/components/form/elements/SelectMultiFormField.tsx +++ b/library/src/components/form/elements/SelectMultiFormField.tsx @@ -3,7 +3,7 @@ import type { FieldValues } from "react-hook-form" import type { FormField } from "../DynamicForm" import { Label, Select } from "../../inputs" -export interface SelectMultiFormField< +export interface SelectMultiFormFieldProps< T extends FieldValues, A extends string | number, > extends FormField { @@ -24,7 +24,7 @@ export function SelectMultiFormField< title, options, placeholder, -}: SelectMultiFormField) { +}: SelectMultiFormFieldProps) { const fieldValue = formProps.watch(name) const onChangeCB = useRef(onChange) if (onChangeCB.current !== onChange) { diff --git a/library/src/components/form/elements/SelectSingleFormField.tsx b/library/src/components/form/elements/SelectSingleFormField.tsx index 5ea0b0be..5a6c2ed9 100644 --- a/library/src/components/form/elements/SelectSingleFormField.tsx +++ b/library/src/components/form/elements/SelectSingleFormField.tsx @@ -3,7 +3,7 @@ import type { FieldValues, Path } from "react-hook-form" import type { FormField } from "../DynamicForm" import { Label, Select } from "../../inputs" -export interface SelectSingleFormField< +export interface SelectSingleFormFieldProps< T extends FieldValues, A extends string | number, > extends FormField { @@ -24,7 +24,7 @@ export function SelectSingleFormField< required, options, placeholder, -}: SelectSingleFormField) { +}: SelectSingleFormFieldProps) { const fieldValue = formProps.watch(name) const onChangeCB = useRef(onChange) if (onChangeCB.current !== onChange) { diff --git a/library/src/components/form/index.ts b/library/src/components/form/index.ts new file mode 100644 index 00000000..13a449bd --- /dev/null +++ b/library/src/components/form/index.ts @@ -0,0 +1,50 @@ +import { + CheckboxFormField, + type CheckboxFormFieldProps as _CheckboxFormFieldProps, +} from "./elements/CheckboxFormField" +import { + InputFormField, + type InputFormFieldProps as _InputFormFieldProps, +} from "./elements/InputFormField" +import { + SelectMultiFormField, + type SelectMultiFormFieldProps as _SelectMultiFormFieldProps, +} from "./elements/SelectMultiFormField" +import { + SelectSingleFormField, + type SelectSingleFormFieldProps as _SelectSingleFormFieldProps, +} from "./elements/SelectSingleFormField" +import { + DynamicForm as Form, + type FormField as _FormField, + type FormProps as _FormProps, + type DynamicFormProps as _DynamicFormProps, +} from "./DynamicForm" +import type { FieldValues } from "react-hook-form" + +const DynamicForm = { + CheckboxFormField, + InputFormField, + SelectMultiFormField, + SelectSingleFormField, + Form, +} +export { DynamicForm } + +export namespace DynamicFormTypes { + export type CheckboxFormFieldProps = + _CheckboxFormFieldProps + export type InputFormFieldProps = + _InputFormFieldProps + export type SelectMultiFormFieldProps< + T extends FieldValues, + A extends string | number, + > = _SelectMultiFormFieldProps + export type SelectSingleFormFieldProps< + T extends FieldValues, + A extends string | number, + > = _SelectSingleFormFieldProps + export type FormField = _FormField + export type FormProps = _FormProps + export type DynamicFormProps = _DynamicFormProps +} diff --git a/showcase/src/components/showcase/wrapper/FormShowcase.tsx b/showcase/src/components/showcase/wrapper/FormShowcase.tsx index 7cc3dbd7..ca8217a3 100644 --- a/showcase/src/components/showcase/wrapper/FormShowcase.tsx +++ b/showcase/src/components/showcase/wrapper/FormShowcase.tsx @@ -1,12 +1,8 @@ import ShowcaseWrapperItem, { type ShowcaseProps, } from "../../ShowCaseWrapperItem/ShowcaseWrapperItem" -import { DynamicForm } from "@linked-planet/ui-kit-ts/components/form/DynamicForm" -import { InputFormField } from "@linked-planet/ui-kit-ts/components/form/elements/InputFormField" -import { SelectSingleFormField } from "@linked-planet/ui-kit-ts/components/form/elements/SelectSingleFormField" -import { CheckboxFormField } from "@linked-planet/ui-kit-ts/components/form/elements/CheckboxFormField" +import { DynamicForm } from "@linked-planet/ui-kit-ts" import { Button, ButtonGroup } from "@linked-planet/ui-kit-ts" -import { SelectMultiFormField } from "@linked-planet/ui-kit-ts/components/form/elements/SelectMultiFormField" interface TestObject { firstname: string @@ -42,7 +38,7 @@ const hobbies = [ function FormVerticalExample() { return (
- + obj={testObject} onSubmit={(data) => { console.info("Saving form", data) @@ -50,17 +46,17 @@ function FormVerticalExample() { > {(formProps) => ( <> - - - - - - )} - +
) } @@ -97,7 +93,7 @@ function FormVerticalExample() { function FormHorizontalExample() { return (
- + vertical obj={testObject} onSubmit={(data) => { @@ -106,19 +102,19 @@ function FormHorizontalExample() { > {(formProps) => ( <> - - - - - - )} - +
) } @@ -155,7 +151,7 @@ function FormHorizontalExample() { function FormCustomExample() { return (
- + hideReset hideSave className="max-w-4xl mt-3" @@ -167,18 +163,18 @@ function FormCustomExample() { {(formProps) => ( <>
- - -
- - - )} - +
) } From 95ee1733f3febcdb807c0ce7c714cda3e233769a Mon Sep 17 00:00:00 2001 From: "Markus T." <19794318+marcus-wishes@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:23:47 +0200 Subject: [PATCH 3/5] fixed usage of ReactDOM.render --- showcase/applayoutexample/index.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/showcase/applayoutexample/index.tsx b/showcase/applayoutexample/index.tsx index 654ba1ff..e1dc60bc 100644 --- a/showcase/applayoutexample/index.tsx +++ b/showcase/applayoutexample/index.tsx @@ -1,11 +1,16 @@ import React from "react" -import ReactDOM from "react-dom" +import { createRoot } from "react-dom/client" import AppLayoutExample from "./AppLayoutExample" -ReactDOM.render( +const container = document.getElementById("applayout-root") +if (!container) { + throw new Error("Could not find root element") +} + +const root = createRoot(container) +root.render( , - document.getElementById("applayout-root"), ) From 2e35a1db508078cd7560d300718d6a148e22cbd2 Mon Sep 17 00:00:00 2001 From: "Markus T." <19794318+marcus-wishes@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:12:11 +0200 Subject: [PATCH 4/5] tour wrapper --- library/src/components/Banner.tsx | 2 +- library/src/components/Blanket.tsx | 7 +- library/src/components/index.ts | 3 + library/src/components/tour/TourWrapper.tsx | 285 ++++++++++------- library/src/components/tour/index.ts | 3 + showcase/public/showcase-sources.txt | 286 ++++++++++-------- .../showcase/wrapper/TourShowcase.tsx | 217 +++++++------ 7 files changed, 452 insertions(+), 351 deletions(-) create mode 100644 library/src/components/tour/index.ts diff --git a/library/src/components/Banner.tsx b/library/src/components/Banner.tsx index e6ac61e3..181f9475 100644 --- a/library/src/components/Banner.tsx +++ b/library/src/components/Banner.tsx @@ -17,7 +17,7 @@ type BannerProps = { } const announcementStyles = "bg-neutral-full text-text-inverse" -const warningStyles = "bg-warning-bold text-text" +const warningStyles = "bg-warning-bold text-text-inverse" const errorStyles = "bg-danger-bold text-text-inverse" const successStyles = "bg-success-bold text-text-inverse" const informationStyles = "bg-information-bold text-text-inverse" diff --git a/library/src/components/Blanket.tsx b/library/src/components/Blanket.tsx index 5eece37d..c4cedaee 100644 --- a/library/src/components/Blanket.tsx +++ b/library/src/components/Blanket.tsx @@ -1,4 +1,3 @@ -import React from "react" import ReactDOM from "react-dom" import type { ComponentPropsWithoutRef } from "react" import { twMerge } from "tailwind-merge" @@ -6,6 +5,7 @@ import { getPortal } from "../utils" type BlanketProps = ComponentPropsWithoutRef<"div"> & { usePortal?: boolean + portalContainer?: HTMLElement | null } export function Blanket({ @@ -14,6 +14,7 @@ export function Blanket({ "aria-label": ariaLabel, role, usePortal = true, + portalContainer = getPortal("uikts-blanket"), ...props }: BlanketProps) { const ele = ( @@ -38,8 +39,8 @@ export function Blanket({ ) - if (!usePortal) { + if (!usePortal || portalContainer === null) { return ele } - return ReactDOM.createPortal(ele, getPortal("uikts-blanket")) + return ReactDOM.createPortal(ele, portalContainer) } diff --git a/library/src/components/index.ts b/library/src/components/index.ts index ee12097e..f0ec09dc 100644 --- a/library/src/components/index.ts +++ b/library/src/components/index.ts @@ -34,3 +34,6 @@ export * from "./Breadcrumbs" export * from "./SectionMessage" export * from "./codeblock" export * from "./dnd" +export * from "./tour" +export * from "./form" +export * from "./EventList" diff --git a/library/src/components/tour/TourWrapper.tsx b/library/src/components/tour/TourWrapper.tsx index 6b8a4b60..43a4fce4 100644 --- a/library/src/components/tour/TourWrapper.tsx +++ b/library/src/components/tour/TourWrapper.tsx @@ -1,8 +1,15 @@ -import ReactJoyride, { type Step } from "react-joyride" -import React, { useRef, useState } from "react" -import {showErrorFlag, showInformationFlag} from "../ToastFlag"; - -export class TourStep { +import ReactJoyride, { + type Locale, + type Styles, + type Step, + type FloaterProps, + type CallBackProps, +} from "react-joyride" +import { useCallback, useMemo, useRef, useState } from "react" +import { showErrorFlag, showInformationFlag } from "../ToastFlag" +import { flushSync } from "react-dom" + +export abstract class TourStep { step: Step constructor() { @@ -12,15 +19,11 @@ export class TourStep { } } - async onInit(next: () => void) { - next() - } + onInit?(): void - async onPrepare(next: () => void) { - next() - } + onPrepare?(): void - async onExit() {} + onExit?(): void } export interface TourProps { @@ -31,137 +34,191 @@ export interface TourProps { showInfoAndError: boolean beforeAll: () => void afterAll: () => void + + /** + * the scroll offset from the top for the tour (to remove the fixed header) + * @default 220 + * */ + scrollOffset?: number + + /** + * Scrolls to the first step element when the tour starts + * @default true + */ + scrollToFirstStep?: boolean + + /** + * Disables the closing of the overlay when clicking outside of the tour + * @default true + * */ + disabledOverlayClose?: boolean +} + +const floaterProps: Partial = { + styles: { + floater: { + zIndex: 2000, + pointerEvents: "auto" as const, + }, + }, +} + +const styles: Partial = { + overlay: { + zIndex: 1000, + //opacity: 0.0, + }, +} + +const locale: Locale = { + back: "Zurück", + close: "Schließen", + last: "Fertig", + next: "Weiter", + open: "Öffnen", + skip: "Überspringen", } export function Tour({ isActive, setActive, steps, - skipOnError, - showInfoAndError, + skipOnError = true, + showInfoAndError = true, beforeAll, afterAll, + scrollOffset = 220, + scrollToFirstStep = true, + disabledOverlayClose = true, }: TourProps) { const [stepIndex, setStepIndex] = useState(0) const isInit = useRef(false) - function next() { - setStepIndex(stepIndex + 1) - } - - function before() { - setStepIndex(stepIndex - 1) - } - - const _steps = steps.map((it) => it.step) - - return ( - { - const { action, index, lifecycle, type } = joyrideState - - const lpBeforeStep = - stepIndex <= 0 ? undefined : steps[stepIndex - 1] - const lpNextStep = - stepIndex >= 0 && stepIndex < steps.length - ? steps[stepIndex + 1] - : undefined - const lpStep = steps[stepIndex] - - if (type === "error" || type === "error:target_not_found") { + // run the set stepIndex update in a timeout that is runs after the rendering is done, and use flushSync to make sure the DOM is updated + const next = useCallback((i: number) => { + window.setTimeout(() => + flushSync(() => setStepIndex((prev) => prev + i)), + ) + }, []) + + const reset = useCallback(() => { + setActive(false) + setStepIndex(0) + isInit.current = false + afterAll() + }, [afterAll, setActive]) + + const _steps = useMemo(() => steps.map((it) => it.step), [steps]) + + const callback = useCallback( + (joyrideState: CallBackProps) => { + const { action, index, lifecycle, type, step } = joyrideState + console.log("ACTION", action, "TYPE", type, "INDEX", index) + + switch (type) { + case "tour:start": + beforeAll() + isInit.current = true + setStepIndex(0) + break + case "tour:end": + reset() + break + case "step:before": + steps[index]?.onPrepare?.() + break + case "step:after": + steps[index]?.onExit?.() + switch (action) { + case "next": + steps[index + 1]?.onInit?.() + next(1) + break + case "prev": + steps[index - 1]?.onInit?.() + next(-1) + break + case "skip": + steps[index + 2]?.onInit?.() + next(2) + break + case "close": + reset() + break + case "reset": + reset() + break + case "stop": + reset() + break + default: + break + } + break + case "error:target_not_found": if (skipOnError) { if (showInfoAndError) { showInformationFlag({ title: "Tour-Info", - description: `Ein Step [${lpStep.step?.title ?? "Unbekannt"}] wurde übersprungen.`, + description: `Ein Step [${steps[index].step?.title ?? "Unbekannt"}] wurde übersprungen, das Element wurde nicht gefunden.`, }) } - next() + next(1) } else { if (showInfoAndError) { showErrorFlag({ title: "Tour-Fehler", - description: `Fehler bei Step [${lpStep.step?.title ?? "Unbekannt"}]. Das Element wurde nicht gefunden.`, + description: `Fehler bei Step [${steps[index].step?.title ?? "Unbekannt"}]. Das Element ${step.target} wurde nicht gefunden.`, }) } - setStepIndex(0) - isInit.current = false + reset() } - afterAll() - } - - if (action === "start" && lifecycle === "init") { - beforeAll() - isInit.current = false - lpStep?.onInit(() => { - isInit.current = true - }) - } - - if (type === "step:after") { - if (action === "prev") { - lpStep?.onExit() - if (lpBeforeStep) { - lpBeforeStep.onPrepare(before) - } else { - before() + break + case "error": + if (skipOnError) { + if (showInfoAndError) { + showInformationFlag({ + title: "Tour-Info", + description: `Ein Step [${steps[index].step?.title ?? "Unbekannt"}] wurde übersprungen.`, + }) } - } else if (action === "next") { - lpStep?.onExit() - if (lpNextStep) { - lpNextStep.onPrepare(next) - } else { - next() + next(1) + } else { + if (showInfoAndError) { + showErrorFlag({ + title: "Tour-Fehler", + description: `Fehler bei Step [${steps[index].step?.title ?? "Unbekannt"}].`, + }) } + reset() } - } - - if (action === "skip" || action === "close") { - setActive(false) - isInit.current = false - afterAll() - } - - if ( - action === "reset" || - (type === "tour:end" && action === "next") - ) { - setActive(false) - setStepIndex(0) - isInit.current = false - afterAll() - } - }} + break + + default: + break + } + }, + [beforeAll, reset, next, showInfoAndError, skipOnError, steps], + ) + + return ( + ) } diff --git a/library/src/components/tour/index.ts b/library/src/components/tour/index.ts new file mode 100644 index 00000000..a8207e0e --- /dev/null +++ b/library/src/components/tour/index.ts @@ -0,0 +1,3 @@ +import { Tour, TourStep, type TourProps } from "./TourWrapper" + +export { Tour, TourStep, type TourProps } diff --git a/showcase/public/showcase-sources.txt b/showcase/public/showcase-sources.txt index 7fe7a1fc..9260da88 100644 --- a/showcase/public/showcase-sources.txt +++ b/showcase/public/showcase-sources.txt @@ -3499,7 +3499,7 @@ function EventListExample() { ) : undefined } - renderEvent={(obj, startDate, endDate) => { + /*renderEvent={(obj, startDate, endDate) => { return (
) - }} + }}*/ /> ) @@ -3556,7 +3556,7 @@ function EventListStartEndExample() { ] return ( -
+
@@ -3613,7 +3613,7 @@ export default function EventListShowcase(props: ShowcaseProps) { sourceCodeExampleId: "event-list", }, { - title: "Custom Start/End-Times", + title: "Custom Start/End-Times and Custom Render", example: , sourceCodeExampleId: "event-list-start-end", }, @@ -4085,12 +4085,8 @@ export default FlagShowcase import ShowcaseWrapperItem, { type ShowcaseProps, } from "../../ShowCaseWrapperItem/ShowcaseWrapperItem" -import { DynamicForm } from "@linked-planet/ui-kit-ts/components/form/DynamicForm" -import { InputFormField } from "@linked-planet/ui-kit-ts/components/form/elements/InputFormField" -import { SelectSingleFormField } from "@linked-planet/ui-kit-ts/components/form/elements/SelectSingleFormField" -import { CheckboxFormField } from "@linked-planet/ui-kit-ts/components/form/elements/CheckboxFormField" +import { DynamicForm } from "@linked-planet/ui-kit-ts" import { Button, ButtonGroup } from "@linked-planet/ui-kit-ts" -import { SelectMultiFormField } from "@linked-planet/ui-kit-ts/components/form/elements/SelectMultiFormField" interface TestObject { firstname: string @@ -4126,7 +4122,7 @@ const hobbies = [ function FormVerticalExample() { return (
- + obj={testObject} onSubmit={(data) => { console.info("Saving form", data) @@ -4134,17 +4130,17 @@ function FormVerticalExample() { > {(formProps) => ( <> - - - - - - )} - +
) } @@ -4181,7 +4177,7 @@ function FormVerticalExample() { function FormHorizontalExample() { return (
- + vertical obj={testObject} onSubmit={(data) => { @@ -4190,19 +4186,19 @@ function FormHorizontalExample() { > {(formProps) => ( <> - - - - - - )} - +
) } @@ -4239,7 +4235,7 @@ function FormHorizontalExample() { function FormCustomExample() { return (
- + hideReset hideSave className="max-w-4xl mt-3" @@ -4251,18 +4247,18 @@ function FormCustomExample() { {(formProps) => ( <>
- - -
- - - )} - +
) } @@ -9022,122 +9018,133 @@ function TooltipShowcase(props: ShowcaseProps) { export default TooltipShowcase -import CrossIcon from "@atlaskit/icon/glyph/cross"; -import {Button, ButtonGroup, Modal, Select} from "@linked-planet/ui-kit-ts"; -import {Tour, TourStep} from "@linked-planet/ui-kit-ts/components/tour/TourWrapper"; -import React, {useState} from "react" -import ReactJoyride, {Step} from "react-joyride"; -import ShowcaseWrapperItem, {type ShowcaseProps,} from "../../ShowCaseWrapperItem/ShowcaseWrapperItem" +import CrossIcon from "@atlaskit/icon/glyph/cross" +import { + Button, + ButtonGroup, + Modal, + Select, + ToastFlagContainer, +} from "@linked-planet/ui-kit-ts" +import { Tour, TourStep } from "@linked-planet/ui-kit-ts" +import { useMemo, useState } from "react" +import ShowcaseWrapperItem, { + type ShowcaseProps, +} from "../../ShowCaseWrapperItem/ShowcaseWrapperItem" +import type { Step } from "react-joyride" //#region tour +const defaultLocale = { + back: "Back", + close: "Close", + last: "Done", + next: "Next", + open: "Open", + skip: "Skip", +} as const + function TourExample() { const [isActive, setActive] = useState(false) const [popup, setPopup] = useState(false) - const defaultLocale = { - back: "Zurück", - close: "Schließen", - last: "Fertig", - next: "Weiter", - open: "Öffnen", - skip: "Überspringen", - } as const - - const InitStep = new (class extends TourStep { - step: Step = { - title: "Tour starten", - target: "#tour-start", - disableBeacon: true, - showSkipButton: false, - placement: "bottom", - locale: defaultLocale, - content: ( - - Mit Klick auf diesen Button können Sie jederzeit die Tour - starten. - - ), - } - })() - - const SecondStep = new (class extends TourStep { - step: Step = { - title: "Button", - target: "*[data-id='Test-1']", - disableBeacon: true, - showSkipButton: false, - placement: "right", - locale: defaultLocale, - content: ( - - Klicken Sie diesen Button um das Popup zu öffnen. - - ), - } - })() - - const PopupStep = new (class extends TourStep { - step: Step = { - title: "Popup", - target: "*[data-id='select']", - disableBeacon: true, - showSkipButton: false, - placement: "right", - locale: defaultLocale, - content: ( - - Hier ist ein Select im Popup, was in der Tour geöffnet wurde. - - ), - } + const steps = useMemo(() => { + const InitStep = new (class extends TourStep { + step: Step = { + title: "Tour starten", + target: "#tour-start", + disableBeacon: true, + showSkipButton: false, + placement: "bottom", + locale: { ...defaultLocale, next: "Start Tour" }, + content: ( + + The first step selects the tour start to start the tour. + + ), + } + })() + + const SecondStep = new (class extends TourStep { + step: Step = { + title: "Button", + target: "#joyride-first", + disableBeacon: true, + showSkipButton: false, + placement: "right", + locale: defaultLocale, + content: ( + + This step selects the popup which would open the popup. + + ), + } + })() + + const ThirdPopupStep = new (class extends TourStep { + step: Step = { + title: "Popup", + target: "#test-select", + disableBeacon: true, + showSkipButton: false, + placement: "right", + locale: defaultLocale, + content: ( + + This step opens the popup and selects the dropdown in + it. + + ), + } - async onPrepare(next: () => void): Promise { - setPopup(true) - setTimeout(() => next(), 500) - } + onInit() { + setPopup(true) + } - async onExit(): Promise { - setPopup(false) - } - })() - - const ThirdStep = new (class extends TourStep { - step: Step = { - title: "Weiterer Button", - target: "*[data-id='Test-2']", - disableBeacon: true, - showSkipButton: false, - placement: "right", - locale: defaultLocale, - content: ( - - This step closes the popup and continues with this button. - - ), - } + onPrepare() { + console.log("prepare message") + } - async onPrepare(next: () => void): Promise { - setPopup(false) - setTimeout(() => next(), 500) - } - })() + onExit() { + setPopup(false) + } + })() + + const FourthStep = new (class extends TourStep { + step: Step = { + title: "Weiterer Button", + target: "#joyride-second", + disableBeacon: true, + showSkipButton: false, + placement: "right", + locale: defaultLocale, + content: ( + + This step closes the popup and continues with this + button. + + ), + } + })() + return [InitStep, SecondStep, ThirdPopupStep, FourthStep] + }, []) return (
- + { // initialize dummy data or other inits before tour starts @@ -9149,16 +9156,26 @@ function TourExample() { }} />
- - + +
{ + console.log("OOPEN POPUP CHANGE", popup, opened) if (!opened) setPopup(false) }} - shouldCloseOnEscapePress={true} + //shouldCloseOnEscapePress={false} + shouldCloseOnOverlayClick={false} // this is required, the show "clicks" outside of the dialog closing the modal, which results in the failing of the next step because the element is not mounted anymore accessibleDialogDescription="This is a modal dialog example" > @@ -9178,7 +9195,8 @@ function TourExample() {

This is the body of the modal.