diff --git a/libs/docs/ui5-webcomponents-fiori/api-files.ts b/libs/docs/ui5-webcomponents-fiori/api-files.ts index 9e102cd22ed..ce5c969adab 100644 --- a/libs/docs/ui5-webcomponents-fiori/api-files.ts +++ b/libs/docs/ui5-webcomponents-fiori/api-files.ts @@ -24,5 +24,6 @@ export const API_FILES = { uploadCollection: ['UploadCollection', 'UploadCollectionItem'], userMenu: ['UserMenu', 'UserMenuAccount', 'UserMenuItem', 'UserMenuItemGroup'], userSettingsDialog: ['UserSettingsAccountView', 'UserSettingsDialog', 'UserSettingsItem', 'UserSettingsView'], - viewSettingsDialog: ['FilterItem', 'FilterItemOption', 'GroupItem', 'SortItem', 'ViewSettingsDialog'] + viewSettingsDialog: ['FilterItem', 'FilterItemOption', 'GroupItem', 'SortItem', 'ViewSettingsDialog'], + wizard: ['Wizard', 'WizardStep'] }; diff --git a/libs/docs/ui5-webcomponents-fiori/docs-data.json b/libs/docs/ui5-webcomponents-fiori/docs-data.json index ce182eac29c..a47ecf03734 100644 --- a/libs/docs/ui5-webcomponents-fiori/docs-data.json +++ b/libs/docs/ui5-webcomponents-fiori/docs-data.json @@ -77,6 +77,10 @@ { "url": "ui5-webcomponents-fiori/view-settings-dialog", "name": "View Settings Dialog" + }, + { + "url": "ui5-webcomponents-fiori/wizard", + "name": "Wizard" } ] } diff --git a/libs/docs/ui5-webcomponents-fiori/docs-routes.ts b/libs/docs/ui5-webcomponents-fiori/docs-routes.ts index 7740967d2e9..a6638690b59 100644 --- a/libs/docs/ui5-webcomponents-fiori/docs-routes.ts +++ b/libs/docs/ui5-webcomponents-fiori/docs-routes.ts @@ -102,6 +102,10 @@ const componentRoutes = [ path: 'view-settings-dialog', loadChildren: () => import('@fundamental-ngx/docs/ui5-webcomponents-fiori/view-settings-dialog').then(configureLibRoutes) + }, + { + path: 'wizard', + loadChildren: () => import('@fundamental-ngx/docs/ui5-webcomponents-fiori/wizard').then(configureLibRoutes) } ]; diff --git a/libs/docs/ui5-webcomponents-fiori/wizard/examples/basic-sample.html b/libs/docs/ui5-webcomponents-fiori/wizard/examples/basic-sample.html new file mode 100644 index 00000000000..5584311fe9e --- /dev/null +++ b/libs/docs/ui5-webcomponents-fiori/wizard/examples/basic-sample.html @@ -0,0 +1,235 @@ +
+ + @for (step of steps(); track $index) { + +
+ @if ($index === 0) { + + {{ step.titleText }} + Please provide information about your product. + + @if (!step1Valid() && productName().length > 0) { + + Please fill in all required fields to continue. + + } + +
+
+ Product Name + +
+ +
+ Product Description + +
+
+ } @if ($index === 1) { + + {{ step.titleText }} + Select a pricing plan for your product. + +
+ + + +
+ } @if ($index === 2) { + + {{ step.titleText }} + Choose your preferred delivery method and additional options. + + @if (!step3Valid()) { + + Please select a delivery method. + + } + +
+ + + +
+ } @if ($index === 3) { + + {{ step.titleText }} + Provide your contact information for order updates. + + @if (!step4Valid() && customerName().length > 0) { + + Please fill in all required fields with valid information. + + } + +
+
+ Full Name + +
+ +
+ Email Address + +
+ +
+ Phone Number + +
+
+ } @if ($index === 4) { + + {{ step.titleText }} + Review your information before submitting. + + + All steps completed! Please review your information below. + + +
+ Product Information +
+ Product Name: + {{ productName() }} +
+
+ Description: + {{ productDescription() }} +
+
+ +
+ Pricing +
+ Selected Plan: + + @if (selectedPrice() === 'basic') { Basic - $9.99/month } @else if (selectedPrice() === + 'professional') { Professional - $29.99/month } @else { Enterprise - $99.99/month } + +
+
+ +
+ Delivery Options +
+ Delivery Method: + + @if (standardDelivery()) { Standard Delivery (Free) } @else if (expressDelivery()) { Express + Delivery ($15.00) } + +
+ @if (giftWrap()) { +
+ Additional: + Gift Wrap ($5.00) +
+ } +
+ +
+ Contact Information +
+ Name: + {{ customerName() }} +
+
+ Email: + {{ customerEmail() }} +
+ @if (customerPhone()) { +
+ Phone: + {{ customerPhone() }} +
+ } +
+ } + + +
+ + Previous + + + @if (!isLastStep()) { + + Next + + } @else { + + Submit + + } +
+
+
+ } +
+
diff --git a/libs/docs/ui5-webcomponents-fiori/wizard/examples/basic-sample.ts b/libs/docs/ui5-webcomponents-fiori/wizard/examples/basic-sample.ts new file mode 100644 index 00000000000..3dbaca5ac79 --- /dev/null +++ b/libs/docs/ui5-webcomponents-fiori/wizard/examples/basic-sample.ts @@ -0,0 +1,223 @@ +import { Component, computed, signal } from '@angular/core'; +import type { UI5WrapperCustomEvent } from '@fundamental-ngx/ui5-webcomponents-base/types'; +import { Wizard } from '@fundamental-ngx/ui5-webcomponents-fiori/wizard'; +import { WizardStep } from '@fundamental-ngx/ui5-webcomponents-fiori/wizard-step'; +import { Button } from '@fundamental-ngx/ui5-webcomponents/button'; +import { CheckBox } from '@fundamental-ngx/ui5-webcomponents/check-box'; +import { Input } from '@fundamental-ngx/ui5-webcomponents/input'; +import { Label } from '@fundamental-ngx/ui5-webcomponents/label'; +import { MessageStrip } from '@fundamental-ngx/ui5-webcomponents/message-strip'; +import { RadioButton } from '@fundamental-ngx/ui5-webcomponents/radio-button'; +import { Text } from '@fundamental-ngx/ui5-webcomponents/text'; +import { TextArea } from '@fundamental-ngx/ui5-webcomponents/text-area'; +import { Title } from '@fundamental-ngx/ui5-webcomponents/title'; + +// Import Fundamental Styles +import 'fundamental-styles/dist/margins.css'; +import 'fundamental-styles/dist/paddings.css'; + +// Import icons +import '@ui5/webcomponents-icons/dist/accept.js'; +import '@ui5/webcomponents-icons/dist/cart.js'; +import '@ui5/webcomponents-icons/dist/employee.js'; +import '@ui5/webcomponents-icons/dist/palette.js'; +import '@ui5/webcomponents-icons/dist/product.js'; + +interface WizardStepData { + titleText: string; + subtitleText?: string; + icon: string; + completed: boolean; + valid: boolean; +} + +@Component({ + selector: 'ui5-doc-wizard-basic-sample', + templateUrl: './basic-sample.html', + standalone: true, + imports: [Wizard, WizardStep, Button, Input, Label, Title, Text, TextArea, CheckBox, RadioButton, MessageStrip] +}) +export class BasicSample { + // Current step index + currentStep = signal(0); + + // Step 1: Product Information + productName = signal(''); + productDescription = signal(''); + + // Step 2: Pricing + selectedPrice = signal('basic'); + + // Step 3: Delivery Options + standardDelivery = signal(true); + expressDelivery = signal(false); + giftWrap = signal(false); + + // Step 4: Contact Information + customerName = signal(''); + customerEmail = signal(''); + customerPhone = signal(''); + + // Computed: Step validation + step1Valid = computed(() => this.productName().trim().length > 0 && this.productDescription().trim().length > 0); + + step2Valid = computed(() => this.selectedPrice().length > 0); + + step3Valid = computed(() => this.standardDelivery() || this.expressDelivery()); + + step4Valid = computed(() => { + const emailValid = this.customerEmail().includes('@'); + return this.customerName().trim().length > 0 && emailValid; + }); + + // Steps configuration + steps = computed(() => [ + { + titleText: 'Product Information', + subtitleText: 'Enter product details', + icon: 'product', + completed: this.currentStep() > 0, + valid: this.step1Valid() + }, + { + titleText: 'Pricing', + subtitleText: 'Select pricing plan', + icon: 'palette', + completed: this.currentStep() > 1, + valid: this.step2Valid() + }, + { + titleText: 'Delivery Options', + subtitleText: 'Choose delivery method', + icon: 'cart', + completed: this.currentStep() > 2, + valid: this.step3Valid() + }, + { + titleText: 'Contact Information', + subtitleText: 'Provide contact details', + icon: 'employee', + completed: this.currentStep() > 3, + valid: this.step4Valid() + }, + { + titleText: 'Summary', + subtitleText: 'Review and confirm', + icon: 'accept', + completed: false, + valid: true + } + ]); + + // Computed: Can navigate to next step + canGoNext = computed(() => { + switch (this.currentStep()) { + case 0: + return this.step1Valid(); + case 1: + return this.step2Valid(); + case 2: + return this.step3Valid(); + case 3: + return this.step4Valid(); + default: + return false; + } + }); + + // Computed: Can navigate to previous step + canGoPrevious = computed(() => this.currentStep() > 0); + + // Computed: Can submit wizard + canSubmit = computed( + () => this.step1Valid() && this.step2Valid() && this.step3Valid() && this.step4Valid() && this.isLastStep() + ); + + // Computed: Is last step + isLastStep = computed(() => this.currentStep() === this.steps().length - 1); + + onStepChange(event: UI5WrapperCustomEvent): void { + const detail = event.detail; + if (detail && detail.step) { + const stepElement = detail.step; + const stepIndex = Array.from(stepElement.parentElement?.children || []).indexOf(stepElement); + if (stepIndex !== -1) { + this.currentStep.set(stepIndex); + } + } + } + + goToNextStep(): void { + if (this.canGoNext() && !this.isLastStep()) { + this.currentStep.update((current) => Math.min(current + 1, this.steps().length - 1)); + } + } + + goToPreviousStep(): void { + if (this.canGoPrevious()) { + this.currentStep.update((current) => Math.max(current - 1, 0)); + } + } + + submitWizard(): void { + if (this.isLastStep()) { + alert('Wizard completed successfully!'); + this.resetWizard(); + } + } + + resetWizard(): void { + this.currentStep.set(0); + this.productName.set(''); + this.productDescription.set(''); + this.selectedPrice.set('basic'); + this.standardDelivery.set(true); + this.expressDelivery.set(false); + this.giftWrap.set(false); + this.customerName.set(''); + this.customerEmail.set(''); + this.customerPhone.set(''); + } + + onProductNameChange(event: UI5WrapperCustomEvent): void { + this.productName.set(event.currentTarget.value); + } + + onProductDescriptionChange(event: UI5WrapperCustomEvent): void { + this.productDescription.set(event.currentTarget.value); + } + + onCustomerNameChange(event: UI5WrapperCustomEvent): void { + this.customerName.set(event.currentTarget.value); + } + + onCustomerEmailChange(event: UI5WrapperCustomEvent): void { + this.customerEmail.set(event.currentTarget.value); + } + + onCustomerPhoneChange(event: UI5WrapperCustomEvent): void { + this.customerPhone.set(event.currentTarget.value); + } + + onPriceChange(price: string): void { + this.selectedPrice.set(price); + } + + onStandardDeliveryChange(event: UI5WrapperCustomEvent): void { + this.standardDelivery.set(event.currentTarget.checked); + if (event.currentTarget.checked) { + this.expressDelivery.set(false); + } + } + + onExpressDeliveryChange(event: UI5WrapperCustomEvent): void { + this.expressDelivery.set(event.currentTarget.checked); + if (event.currentTarget.checked) { + this.standardDelivery.set(false); + } + } + + onGiftWrapChange(event: UI5WrapperCustomEvent): void { + this.giftWrap.set(event.currentTarget.checked); + } +} diff --git a/libs/docs/ui5-webcomponents-fiori/wizard/header/wizard-header.html b/libs/docs/ui5-webcomponents-fiori/wizard/header/wizard-header.html new file mode 100644 index 00000000000..6678ef400c4 --- /dev/null +++ b/libs/docs/ui5-webcomponents-fiori/wizard/header/wizard-header.html @@ -0,0 +1,9 @@ + +
{{componentName}}
+ + + + + + +
diff --git a/libs/docs/ui5-webcomponents-fiori/wizard/header/wizard-header.ts b/libs/docs/ui5-webcomponents-fiori/wizard/header/wizard-header.ts new file mode 100644 index 00000000000..4c63eac3ca5 --- /dev/null +++ b/libs/docs/ui5-webcomponents-fiori/wizard/header/wizard-header.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; +import { + DescriptionComponent, + DocPageComponent, + HeaderComponent, + HeaderTabsComponent, + ImportComponent +} from '@fundamental-ngx/docs/shared'; + +@Component({ + selector: 'ui5-wizard-header', + templateUrl: './wizard-header.html', + standalone: true, + imports: [DocPageComponent, HeaderComponent, DescriptionComponent, ImportComponent, HeaderTabsComponent] +}) +export class WizardHeader { + componentName = 'Wizard'; + packageName = '@ui5/webcomponents-fiori'; +} diff --git a/libs/docs/ui5-webcomponents-fiori/wizard/index.ts b/libs/docs/ui5-webcomponents-fiori/wizard/index.ts new file mode 100644 index 00000000000..5943042ff8c --- /dev/null +++ b/libs/docs/ui5-webcomponents-fiori/wizard/index.ts @@ -0,0 +1,19 @@ +import { Routes } from '@angular/router'; +import { WizardHeader } from './header/wizard-header'; +import { WizardDocs } from './wizard-docs'; + +export const ROUTES: Routes = [ + { + path: '', + component: WizardHeader, + data: { primary: true }, + children: [ + { + path: '', + component: WizardDocs + } + ] + } +]; +export const LIBRARY_NAME = 'wizard'; +export const API_FILE_KEY = 'wizard'; diff --git a/libs/docs/ui5-webcomponents-fiori/wizard/project.json b/libs/docs/ui5-webcomponents-fiori/wizard/project.json new file mode 100644 index 00000000000..f6c38bf02fc --- /dev/null +++ b/libs/docs/ui5-webcomponents-fiori/wizard/project.json @@ -0,0 +1,9 @@ +{ + "name": "docs-ui5-webcomponents-fiori-wizard", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/docs/ui5-webcomponents-fiori/wizard", + "prefix": "ui5-doc", + "projectType": "library", + "tags": ["type:lib", "scope:docs"], + "targets": {} +} diff --git a/libs/docs/ui5-webcomponents-fiori/wizard/tsconfig.json b/libs/docs/ui5-webcomponents-fiori/wizard/tsconfig.json new file mode 100644 index 00000000000..26b852b9336 --- /dev/null +++ b/libs/docs/ui5-webcomponents-fiori/wizard/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": ["**/*.ts"], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": false, + "noFallthroughCasesInSwitch": true, + "target": "es2020", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": ["**/*.test.ts", "**/*.spec.ts", "**/*.e2e-spec.ts"] +} diff --git a/libs/docs/ui5-webcomponents-fiori/wizard/wizard-docs.html b/libs/docs/ui5-webcomponents-fiori/wizard/wizard-docs.html new file mode 100644 index 00000000000..26af1c8c97c --- /dev/null +++ b/libs/docs/ui5-webcomponents-fiori/wizard/wizard-docs.html @@ -0,0 +1,10 @@ + Basic Wizard Example + + The Wizard component guides users through a multi-step process. Each step can have a title, subtitle, and icon. + Users can navigate between steps by clicking the step headers or using the navigation buttons. The example below + demonstrates a product configuration wizard with validation and step completion tracking. + + + + + diff --git a/libs/docs/ui5-webcomponents-fiori/wizard/wizard-docs.ts b/libs/docs/ui5-webcomponents-fiori/wizard/wizard-docs.ts new file mode 100644 index 00000000000..2b1872355ae --- /dev/null +++ b/libs/docs/ui5-webcomponents-fiori/wizard/wizard-docs.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { + CodeExampleComponent, + ComponentExampleComponent, + DescriptionComponent, + DocsSectionTitleComponent, + ExampleFile, + getAssetFromModuleAssets +} from '@fundamental-ngx/docs/shared'; +import { BasicSample } from './examples/basic-sample'; + +const basicSampleTs = 'basic-sample.ts'; +const basicSampleHtml = 'basic-sample.html'; + +@Component({ + selector: 'ui5-wizard-docs', + templateUrl: './wizard-docs.html', + standalone: true, + imports: [ + CodeExampleComponent, + ComponentExampleComponent, + DescriptionComponent, + DocsSectionTitleComponent, + BasicSample + ] +}) +export class WizardDocs { + basicExample: ExampleFile[] = [ + { + language: 'typescript', + fileName: 'basic-sample', + code: getAssetFromModuleAssets(basicSampleTs), + component: 'BasicSample' + }, + { + language: 'html', + fileName: 'basic-sample', + code: getAssetFromModuleAssets(basicSampleHtml), + scssFileCode: getAssetFromModuleAssets(basicSampleHtml) + } + ]; +}