From 637b9c04b25be4d85084cb0da74d02d59cc474fc Mon Sep 17 00:00:00 2001 From: Romet Pastak Date: Mon, 10 Nov 2025 15:40:32 +0200 Subject: [PATCH 1/3] feat(checkbox): add TEDI-Ready checkbox component #74 --- .../form/checkbox/checkbox.stories.ts | 5 + .../form/checkbox/checkbox.component.scss | 102 +++++ .../form/checkbox/checkbox.component.spec.ts | 41 ++ .../form/checkbox/checkbox.component.ts | 33 ++ .../form/checkbox/checkbox.stories.ts | 352 ++++++++++++++++++ tedi/components/form/index.ts | 3 +- .../form/label/label.component.scss | 15 +- tedi/components/form/label/label.component.ts | 8 +- tedi/components/form/label/label.stories.ts | 14 + 9 files changed, 569 insertions(+), 4 deletions(-) create mode 100644 tedi/components/form/checkbox/checkbox.component.scss create mode 100644 tedi/components/form/checkbox/checkbox.component.spec.ts create mode 100644 tedi/components/form/checkbox/checkbox.component.ts create mode 100644 tedi/components/form/checkbox/checkbox.stories.ts diff --git a/community/components/form/checkbox/checkbox.stories.ts b/community/components/form/checkbox/checkbox.stories.ts index 6ae5e376a..849e6a231 100644 --- a/community/components/form/checkbox/checkbox.stories.ts +++ b/community/components/form/checkbox/checkbox.stories.ts @@ -14,6 +14,11 @@ import { CheckboxCardGroupComponent } from "./checkbox-card-group/checkbox-card- export default { title: "Community/Form/Checkbox", component: CheckboxComponent, + parameters: { + status: { + type: ["existsInTediReady"], + }, + }, args: { size: "default", disabled: false, diff --git a/tedi/components/form/checkbox/checkbox.component.scss b/tedi/components/form/checkbox/checkbox.component.scss new file mode 100644 index 000000000..b81ad31e1 --- /dev/null +++ b/tedi/components/form/checkbox/checkbox.component.scss @@ -0,0 +1,102 @@ +@use "@tedi-design-system/core/mixins"; +@use "@tedi-design-system/core/bootstrap-utility/breakpoints"; + +input[tedi-checkbox][type="checkbox"] { + --_checkbox-icon-size: 1.125rem; + + appearance: none; + position: relative; + cursor: pointer; + border: 1px solid var(--form-checkbox-radio-default-border-default); + background-color: var(--form-checkbox-radio-default-background-default); + vertical-align: middle; + padding: 0; + margin: 0; + + @include mixins.responsive-styles(width, form-checkbox-radio-size-responsive); + @include mixins.responsive-styles( + height, + form-checkbox-radio-size-responsive + ); + @include mixins.responsive-styles( + border-radius, + form-checkbox-radio-indicator-radius-checkbox + ); + + @include breakpoints.media-breakpoint-up(sm) { + --_checkbox-icon-size: 1rem; + } + + &:not(:disabled):hover { + border-color: var(--form-checkbox-radio-default-border-hover); + box-shadow: 0 0 0 1px var(--form-checkbox-radio-default-border-hover); + } + + &:not(:checked):disabled { + cursor: not-allowed; + border-color: var(--form-general-border-disabled); + background-color: var(--form-general-background-disabled); + } + + &:checked, + &:indeterminate { + border-color: var(--form-checkbox-radio-default-border-selected); + background-color: var(--form-checkbox-radio-default-background-selected); + + &:disabled { + cursor: not-allowed; + border-color: var(--form-checkbox-radio-default-border-selected-disabled); + background-color: var( + --form-checkbox-radio-default-background-selected-disabled + ); + } + + &::before { + position: absolute; + top: 50%; + left: 50%; + content: "check"; + font-size: var(--_checkbox-icon-size); + font-family: "Material Symbols Outlined", sans-serif; + -webkit-font-smoothing: antialiased; + color: var(--form-checkbox-radio-default-check-indicator-default); + line-height: 1; + transform: translate(-50%, -50%); + } + } + + &:checked { + &::before { + content: "check"; + } + } + + &:indeterminate { + &::before { + content: "remove"; + } + } + + &:focus-visible { + outline-width: 2px; + outline-offset: 2px; + outline-style: solid; + } + + &:not(:checked):not(:disabled).tedi-checkbox--invalid, + &:user-invalid, + &.ng-invalid.ng-touched { + border-color: var(--form-general-feedback-error-border); + + &:hover { + border-color: var(--form-checkbox-radio-default-border-hover); + } + } + + &.tedi-checkbox--large { + --_checkbox-icon-size: 1.125rem; + + @include mixins.responsive-styles(width, form-checkbox-radio-size-large); + @include mixins.responsive-styles(height, form-checkbox-radio-size-large); + } +} diff --git a/tedi/components/form/checkbox/checkbox.component.spec.ts b/tedi/components/form/checkbox/checkbox.component.spec.ts new file mode 100644 index 000000000..a0d4dbb50 --- /dev/null +++ b/tedi/components/form/checkbox/checkbox.component.spec.ts @@ -0,0 +1,41 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { CheckboxComponent } from "./checkbox.component"; + +describe("CheckboxComponent", () => { + let fixture: ComponentFixture; + let element: HTMLInputElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [CheckboxComponent], + }); + + fixture = TestBed.createComponent(CheckboxComponent); + element = fixture.nativeElement; + fixture.detectChanges(); + }); + + it("should create component", () => { + expect(fixture.componentInstance).toBeTruthy(); + }); + + it("should not have large class by default", () => { + expect(element.classList).not.toContain("tedi-checkbox--large"); + }); + + it("should apply large class", () => { + fixture.componentRef.setInput("size", "large"); + fixture.detectChanges(); + expect(element.classList).toContain("tedi-checkbox--large"); + }); + + it("should not have invalid class by default", () => { + expect(element.classList).not.toContain("tedi-checkbox--invalid"); + }); + + it("should apply invalid class", () => { + fixture.componentRef.setInput("invalid", true); + fixture.detectChanges(); + expect(element.classList).toContain("tedi-checkbox--invalid"); + }); +}); diff --git a/tedi/components/form/checkbox/checkbox.component.ts b/tedi/components/form/checkbox/checkbox.component.ts new file mode 100644 index 000000000..f6d20fb0b --- /dev/null +++ b/tedi/components/form/checkbox/checkbox.component.ts @@ -0,0 +1,33 @@ +import { + ChangeDetectionStrategy, + Component, + input, + ViewEncapsulation, +} from "@angular/core"; + +export type CheckboxSize = "default" | "large"; + +@Component({ + standalone: true, + selector: "input[type=checkbox][tedi-checkbox]", + template: "", + styleUrl: "./checkbox.component.scss", + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + "[class.tedi-checkbox--large]": "size() === 'large'", + "[class.tedi-checkbox--invalid]": "invalid()", + }, +}) +export class CheckboxComponent { + /** + * Size of the checkbox. + * @default default + */ + readonly size = input("default"); + /** + * Is checkbox invalid? + * @default false + */ + readonly invalid = input(false); +} diff --git a/tedi/components/form/checkbox/checkbox.stories.ts b/tedi/components/form/checkbox/checkbox.stories.ts new file mode 100644 index 000000000..8319f2de9 --- /dev/null +++ b/tedi/components/form/checkbox/checkbox.stories.ts @@ -0,0 +1,352 @@ +import { + argsToTemplate, + Meta, + moduleMetadata, + StoryObj, +} from "@storybook/angular"; +import { CheckboxComponent } from "./checkbox.component"; +import { RowComponent } from "../../helpers/grid/row/row.component"; +import { TextComponent } from "../../base/text/text.component"; +import { LabelComponent } from "../label/label.component"; +import { IconComponent } from "../../base/icon/icon.component"; +import { TooltipComponent } from "../../overlay/tooltip/tooltip.component"; +import { TooltipTriggerComponent } from "../../overlay/tooltip/tooltip-trigger/tooltip-trigger.component"; +import { TooltipContentComponent } from "../../overlay/tooltip/tooltip-content/tooltip-content.component"; +import { InfoButtonComponent } from "../../buttons/info-button/info-button.component"; +import { FeedbackTextComponent } from "../feedback-text/feedback-text.component"; + +/** + * Figma ↗
+ * Zeroheight ↗ + */ + +export default { + title: "TEDI-Ready/Components/Form/Checkbox", + component: CheckboxComponent, + decorators: [ + moduleMetadata({ + imports: [ + CheckboxComponent, + RowComponent, + TextComponent, + LabelComponent, + IconComponent, + TooltipComponent, + TooltipTriggerComponent, + TooltipContentComponent, + InfoButtonComponent, + FeedbackTextComponent, + ], + }), + ], + argTypes: { + size: { + control: "radio", + options: ["default", "large"], + description: "Size of the checkbox.", + table: { + type: { + summary: "CheckboxSize", + detail: "default \nlarge", + }, + defaultValue: { + summary: "default", + }, + }, + }, + invalid: { + control: "boolean", + description: "Is checkbox invalid?", + table: { + type: { + summary: "boolean", + }, + defaultValue: { + summary: "false", + }, + }, + }, + disabled: { + control: "boolean", + description: "Is checkbox disabled?", + table: { + type: { + summary: "boolean", + }, + }, + }, + }, +} as Meta; + +export const Default: StoryObj = { + args: { + size: "default", + invalid: false, + disabled: false, + }, + render: (args) => ({ + props: args, + template: ` + + `, + }), +}; + +export const Size: StoryObj = { + render: (args) => ({ + props: args, + template: ` + +
Default
+ +
Large
+ +
+ `, + }), +}; + +export const Vertical: StoryObj = { + render: (args) => ({ + props: args, + template: ` +

Label

+ + + + + + `, + }), +}; + +export const Horizontal: StoryObj = { + render: (args) => ({ + props: args, + template: ` +

Label

+
+ + + +
+ `, + }), +}; + +export const VerticalTree: StoryObj = { + render: (args) => { + setTimeout(() => { + const parent = document.querySelector( + "#parentCB input", + ) as HTMLInputElement; + const children = Array.from( + document.querySelectorAll("#childrenCB input"), + ) as HTMLInputElement[]; + + function updateParent() { + const checked = children.map((c) => c.checked); + const all = checked.every((v) => v === true); + const none = checked.every((v) => v === false); + + parent.checked = all; + parent.indeterminate = !all && !none; + } + + updateParent(); + + children.forEach((c) => c.addEventListener("change", updateParent)); + + parent.addEventListener("change", () => { + const targetState = parent.checked; + children.forEach((c) => (c.checked = targetState)); + updateParent(); + }); + }); + + return { + props: args, + template: ` +

Label

+ + + + + + + + + `, + }; + }, +}; + +export const Separate: StoryObj = { + render: (args) => ({ + props: args, + template: ` + + +
+ + +
+ +
+ + + + + + + Tooltip text + + +
+
+
+ +
+
+ +

+ Description +

+
+
+
+ `, + }), +}; + +export const Group: StoryObj = { + render: (args) => ({ + props: args, + template: ` + +
+

Label

+ + + + + + +
+
+

Label

+
+ + + +
+ +
+
+

Label

+ + + + + + +
+
+

Label

+
+ + + +
+ +
+
+ `, + }), +}; diff --git a/tedi/components/form/index.ts b/tedi/components/form/index.ts index 09935c35a..e8ac0177e 100644 --- a/tedi/components/form/index.ts +++ b/tedi/components/form/index.ts @@ -1,4 +1,5 @@ +export * from "./checkbox/checkbox.component"; export * from "./feedback-text/feedback-text.component"; export * from "./label/label.component"; export * from "./number-field/number-field.component"; -export * from "./toggle/toggle.component"; \ No newline at end of file +export * from "./toggle/toggle.component"; diff --git a/tedi/components/form/label/label.component.scss b/tedi/components/form/label/label.component.scss index 622e2c15f..adabd14f0 100644 --- a/tedi/components/form/label/label.component.scss +++ b/tedi/components/form/label/label.component.scss @@ -1,8 +1,6 @@ @use "@tedi-design-system/core/mixins"; .tedi-label { - color: var(--general-text-secondary); - @include mixins.responsive-styles( font-size, body-regular-size, @@ -28,4 +26,17 @@ color: var(--form-general-feedback-error-border); } } + + &--primary { + color: var(--general-text-primary); + } + + &--secondary { + color: var(--general-text-secondary); + } + + &:has(input[disabled]), + &[for]:has(+ input[disabled]) { + color: var(--general-text-disabled); + } } diff --git a/tedi/components/form/label/label.component.ts b/tedi/components/form/label/label.component.ts index 807e6f024..7f17f23af 100644 --- a/tedi/components/form/label/label.component.ts +++ b/tedi/components/form/label/label.component.ts @@ -7,6 +7,7 @@ import { } from "@angular/core"; export type LabelSize = "small" | "default"; +export type LabelColor = "primary" | "secondary"; @Component({ selector: "label[tedi-label]", @@ -30,9 +31,14 @@ export class LabelComponent { * @default false */ required = input(false); + /** + * Color of the label. + * @default secondary + */ + color = input("secondary"); classes = computed(() => { - const classList = ["tedi-label"]; + const classList = ["tedi-label", `tedi-label--${this.color()}`]; if (this.size() === "small") { classList.push("tedi-label--small"); diff --git a/tedi/components/form/label/label.stories.ts b/tedi/components/form/label/label.stories.ts index e18b7b909..fad1d2bb7 100644 --- a/tedi/components/form/label/label.stories.ts +++ b/tedi/components/form/label/label.stories.ts @@ -63,6 +63,19 @@ export default { }, }, }, + color: { + control: "radio", + description: "Color of the label", + options: ["primary", "secondary"], + table: { + category: "inputs", + type: { + summary: "LabelColor", + detail: "primary \nsecondary", + }, + defaultValue: { summary: "secondary" }, + }, + }, }, } as Meta; @@ -72,6 +85,7 @@ export const Default: LabelStory = { args: { ngContent: "Label", size: "default", + color: "secondary", }, render: ({ ngContent, ...args }) => ({ props: args, From b16f13b1d50ac76980de64105c0d97ab5dd84c6a Mon Sep 17 00:00:00 2001 From: Romet Pastak Date: Mon, 24 Nov 2025 09:10:51 +0200 Subject: [PATCH 2/3] feat(checkbox): merge rc into checkbox #74 --- CHANGELOG.md | 12 + .../file-dropzone.component.html | 80 ++-- .../file-dropzone.component.scss | 4 + .../modal/footer/modal-footer.component.scss | 2 +- .../modal/header/modal-header.component.scss | 2 +- .../overlay/modal/modal.component.scss | 2 +- .../components/overlay/modal/modal.stories.ts | 5 + package-lock.json | 8 +- package.json | 2 +- tedi/components/overlay/index.ts | 3 +- tedi/components/overlay/modal/index.ts | 4 + .../modal-content.component.html | 1 + .../modal-content/modal-content.component.ts | 16 + .../modal-footer/modal-footer.component.ts | 15 + .../modal-header/modal-header.component.html | 7 + .../modal-header.component.spec.ts | 85 ++++ .../modal-header/modal-header.component.ts | 29 ++ .../overlay/modal/modal.component.html | 15 + .../overlay/modal/modal.component.scss | 129 ++++++ .../overlay/modal/modal.component.spec.ts | 138 ++++++ .../overlay/modal/modal.component.ts | 121 ++++++ .../components/overlay/modal/modal.stories.ts | 399 ++++++++++++++++++ 22 files changed, 1031 insertions(+), 48 deletions(-) create mode 100644 tedi/components/overlay/modal/index.ts create mode 100644 tedi/components/overlay/modal/modal-content/modal-content.component.html create mode 100644 tedi/components/overlay/modal/modal-content/modal-content.component.ts create mode 100644 tedi/components/overlay/modal/modal-footer/modal-footer.component.ts create mode 100644 tedi/components/overlay/modal/modal-header/modal-header.component.html create mode 100644 tedi/components/overlay/modal/modal-header/modal-header.component.spec.ts create mode 100644 tedi/components/overlay/modal/modal-header/modal-header.component.ts create mode 100644 tedi/components/overlay/modal/modal.component.html create mode 100644 tedi/components/overlay/modal/modal.component.scss create mode 100644 tedi/components/overlay/modal/modal.component.spec.ts create mode 100644 tedi/components/overlay/modal/modal.component.ts create mode 100644 tedi/components/overlay/modal/modal.stories.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3748cc2d8..ae03b4cfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# [4.1.0-rc.1](https://github.com/TEDI-Design-System/angular/compare/angular-4.0.1-rc.2...angular-4.1.0-rc.1) (2025-11-14) + + +### Bug Fixes + +* **file-dropzone:** fix aligment of info button in file-dropzone [#186](https://github.com/TEDI-Design-System/angular/issues/186) ([bb94cb1](https://github.com/TEDI-Design-System/angular/commit/bb94cb14e411506deb0df6fd6272be5ea75a8572)) + + +### Features + +* **file-dropzone:** add ng-content for file list [#186](https://github.com/TEDI-Design-System/angular/issues/186) ([8dd9c81](https://github.com/TEDI-Design-System/angular/commit/8dd9c81224ae75360a979ec6ec2d47589bcef338)) + ## [4.0.1-rc.2](https://github.com/TEDI-Design-System/angular/compare/angular-4.0.1-rc.1...angular-4.0.1-rc.2) (2025-11-04) diff --git a/community/components/form/file-dropzone/file-dropzone.component.html b/community/components/form/file-dropzone/file-dropzone.component.html index 6e1aab398..c44064015 100644 --- a/community/components/form/file-dropzone/file-dropzone.component.html +++ b/community/components/form/file-dropzone/file-dropzone.component.html @@ -32,47 +32,49 @@ } -@for (file of files(); track file.name) { -
- - -
- {{ file.label ?? file.name }} + + @for (file of files(); track file.name) { +
+ + +
+ {{ file.label ?? file.name }} - @if (file && validateIndividually() && file.helper; as helper) { - - - - + @if (file && validateIndividually() && file.helper; as helper) { + + + + - - {{ helper.text }} - - - } -
- -
-
-
-} + + {{ helper.text }} + + + } +
+ +
+
+
+ } + @if (this.uploadError(); as message) { diff --git a/community/components/form/file-dropzone/file-dropzone.component.scss b/community/components/form/file-dropzone/file-dropzone.component.scss index 97fc1c6cb..1ea8c1f65 100644 --- a/community/components/form/file-dropzone/file-dropzone.component.scss +++ b/community/components/form/file-dropzone/file-dropzone.component.scss @@ -17,3 +17,7 @@ .tedi-file-dropzone__tooltip--error .tedi-info-button { --button-main-neutral-text-default: var(--red-700); } + +.tedi-file-dropzone__file-name { + display: flex; +} diff --git a/community/components/overlay/modal/footer/modal-footer.component.scss b/community/components/overlay/modal/footer/modal-footer.component.scss index 44c1ca059..ae936e7d2 100644 --- a/community/components/overlay/modal/footer/modal-footer.component.scss +++ b/community/components/overlay/modal/footer/modal-footer.component.scss @@ -4,7 +4,7 @@ --_modal-footer-gap: 0.5rem; display: flex; gap: var(--_modal-footer-gap); - border-top: var(--_modal-border); + border-top: var(--borders-01) solid var(--modal-border-inner); padding: var(--_tedi-modal-footer-padding-y) var(--_tedi-modal-footer-padding-x); diff --git a/community/components/overlay/modal/header/modal-header.component.scss b/community/components/overlay/modal/header/modal-header.component.scss index a5adcacc2..29c7635a5 100644 --- a/community/components/overlay/modal/header/modal-header.component.scss +++ b/community/components/overlay/modal/header/modal-header.component.scss @@ -1,7 +1,7 @@ @use "@tedi-design-system/core/mixins"; .tedi-modal-header { - border-bottom: var(--_modal-border); + border-bottom: var(--borders-01) solid var(--modal-border-inner); padding: var(--_tedi-modal-heading-padding-y) var(--_tedi-modal-heading-padding-x); diff --git a/community/components/overlay/modal/modal.component.scss b/community/components/overlay/modal/modal.component.scss index 7db1b677a..ffd164257 100644 --- a/community/components/overlay/modal/modal.component.scss +++ b/community/components/overlay/modal/modal.component.scss @@ -27,7 +27,7 @@ $modal-breapoints: (xs, sm, md, lg, xl); } .tedi-modal { - --_modal-border: var(--borders-01) solid var(--modal-border); + --_modal-border: var(--borders-01) solid var(--modal-border-outer); --_modal-padding: var(--dimensions-13); overflow: auto; diff --git a/community/components/overlay/modal/modal.stories.ts b/community/components/overlay/modal/modal.stories.ts index 9bee2a71b..c4236c4bf 100644 --- a/community/components/overlay/modal/modal.stories.ts +++ b/community/components/overlay/modal/modal.stories.ts @@ -114,6 +114,11 @@ const meta: Meta = { imports: [ModalOpenComponent, StorybookModalComponent], }), ], + parameters: { + status: { + type: ["existsInTediReady"], + }, + }, argTypes: { maxWidth: { control: { diff --git a/package-lock.json b/package-lock.json index d6b600d61..71aeed9fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "@tedi-design-system/angular", "version": "0.0.0-semantic-version", "dependencies": { - "@tedi-design-system/core": "^2.0.0" + "@tedi-design-system/core": "^2.4.0" }, "devDependencies": { "@angular-devkit/core": "19.2.15", @@ -9423,9 +9423,9 @@ } }, "node_modules/@tedi-design-system/core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tedi-design-system/core/-/core-2.0.0.tgz", - "integrity": "sha512-ODuJgRoQYxyvs702iphOuRd68vTBpmIEe/ol4rFssSVDAZ+pZOCbfd87qvoEHW+iDWDlsxHJIOrzMTl3qsTHAQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@tedi-design-system/core/-/core-2.4.0.tgz", + "integrity": "sha512-tLr2Yf/LwGDCBnaqO/Ar2XEYPpZkcBC/K42hxHReN+EY4BbQyzcbU1W8egQJlgfvHjaKSxXSsnZ8SNC0PMe3vA==", "engines": { "node": ">=18.0.0", "npm": ">=8.0.0" diff --git a/package.json b/package.json index d0436c693..384f6cf20 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "ngx-float-ui": "^19.0.1 || ^20.0.0" }, "dependencies": { - "@tedi-design-system/core": "^2.0.0" + "@tedi-design-system/core": "^2.4.0" }, "devDependencies": { "@angular-devkit/core": "19.2.15", diff --git a/tedi/components/overlay/index.ts b/tedi/components/overlay/index.ts index 7439217de..7b55370d8 100644 --- a/tedi/components/overlay/index.ts +++ b/tedi/components/overlay/index.ts @@ -1,2 +1,3 @@ +export * from "./modal"; export * from "./tooltip"; -export * from "./popover"; \ No newline at end of file +export * from "./popover"; diff --git a/tedi/components/overlay/modal/index.ts b/tedi/components/overlay/modal/index.ts new file mode 100644 index 000000000..c510b7b2b --- /dev/null +++ b/tedi/components/overlay/modal/index.ts @@ -0,0 +1,4 @@ +export * from "./modal.component"; +export * from "./modal-content/modal-content.component"; +export * from "./modal-footer/modal-footer.component"; +export * from "./modal-header/modal-header.component"; diff --git a/tedi/components/overlay/modal/modal-content/modal-content.component.html b/tedi/components/overlay/modal/modal-content/modal-content.component.html new file mode 100644 index 000000000..40b372640 --- /dev/null +++ b/tedi/components/overlay/modal/modal-content/modal-content.component.html @@ -0,0 +1 @@ + diff --git a/tedi/components/overlay/modal/modal-content/modal-content.component.ts b/tedi/components/overlay/modal/modal-content/modal-content.component.ts new file mode 100644 index 000000000..12679a1a3 --- /dev/null +++ b/tedi/components/overlay/modal/modal-content/modal-content.component.ts @@ -0,0 +1,16 @@ +import { + Component, + ViewEncapsulation, + ChangeDetectionStrategy, +} from "@angular/core"; + +@Component({ + standalone: true, + selector: "tedi-modal-content", + imports: [], + templateUrl: "./modal-content.component.html", + styleUrl: "../modal.component.scss", + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ModalContentComponent {} diff --git a/tedi/components/overlay/modal/modal-footer/modal-footer.component.ts b/tedi/components/overlay/modal/modal-footer/modal-footer.component.ts new file mode 100644 index 000000000..89f2e9539 --- /dev/null +++ b/tedi/components/overlay/modal/modal-footer/modal-footer.component.ts @@ -0,0 +1,15 @@ +import { + Component, + ViewEncapsulation, + ChangeDetectionStrategy, +} from "@angular/core"; + +@Component({ + standalone: true, + selector: "tedi-modal-footer", + template: "", + styleUrl: "../modal.component.scss", + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ModalFooterComponent {} diff --git a/tedi/components/overlay/modal/modal-header/modal-header.component.html b/tedi/components/overlay/modal/modal-header/modal-header.component.html new file mode 100644 index 000000000..6898a52dc --- /dev/null +++ b/tedi/components/overlay/modal/modal-header/modal-header.component.html @@ -0,0 +1,7 @@ +
+ + @if (showClose()) { + + } +
+ diff --git a/tedi/components/overlay/modal/modal-header/modal-header.component.spec.ts b/tedi/components/overlay/modal/modal-header/modal-header.component.spec.ts new file mode 100644 index 000000000..06072a375 --- /dev/null +++ b/tedi/components/overlay/modal/modal-header/modal-header.component.spec.ts @@ -0,0 +1,85 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { Component } from "@angular/core"; +import { ModalHeaderComponent } from "./modal-header.component"; +import { ModalComponent } from "../modal.component"; +import { viewChild } from "@angular/core"; + +class MockModalComponent { + open = { + value: false, + set: jest.fn(function (val: boolean) { + this.value = val; + }), + }; +} + +@Component({ + standalone: true, + imports: [ModalHeaderComponent], + template: ` + + Header Content + + `, +}) +class TestHostComponent { + showClose = true; + header = viewChild.required(ModalHeaderComponent); +} + +describe("ModalHeaderComponent", () => { + let fixture: ComponentFixture; + let host: TestHostComponent; + let component: ModalHeaderComponent; + let modal: MockModalComponent; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TestHostComponent], + providers: [{ provide: ModalComponent, useClass: MockModalComponent }], + }); + + fixture = TestBed.createComponent(TestHostComponent); + fixture.detectChanges(); + + host = fixture.componentInstance; + component = host.header(); + modal = TestBed.inject(ModalComponent) as unknown as MockModalComponent; + }); + + it("should create the component", () => { + expect(component).toBeTruthy(); + }); + + it("should show close button when showClose = true", () => { + const btn = fixture.nativeElement.querySelector("button"); + expect(btn).not.toBeNull(); + }); + + it("should hide close button when showClose = false", () => { + host.showClose = false; + fixture.detectChanges(); + + const btn = fixture.nativeElement.querySelector("button"); + expect(btn).toBeNull(); + }); + + it("should close modal when close button is clicked", () => { + const btn = fixture.nativeElement.querySelector("button")!; + modal.open.value = true; + + btn.click(); + fixture.detectChanges(); + + expect(modal.open.set).toHaveBeenCalledWith(false); + }); + + it("should close modal via closeModal() method", () => { + modal.open.value = true; + + component.closeModal(); + fixture.detectChanges(); + + expect(modal.open.set).toHaveBeenCalledWith(false); + }); +}); diff --git a/tedi/components/overlay/modal/modal-header/modal-header.component.ts b/tedi/components/overlay/modal/modal-header/modal-header.component.ts new file mode 100644 index 000000000..bd98567fa --- /dev/null +++ b/tedi/components/overlay/modal/modal-header/modal-header.component.ts @@ -0,0 +1,29 @@ +import { + Component, + ViewEncapsulation, + ChangeDetectionStrategy, + input, + inject, +} from "@angular/core"; +import { ClosingButtonComponent } from "../../../buttons/closing-button/closing-button.component"; +import { ModalComponent } from "../modal.component"; + +@Component({ + standalone: true, + selector: "tedi-modal-header", + imports: [ClosingButtonComponent], + templateUrl: "./modal-header.component.html", + styleUrl: "../modal.component.scss", + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ModalHeaderComponent { + /** Should show closing button? */ + readonly showClose = input(true); + + private readonly modal = inject(ModalComponent); + + closeModal() { + this.modal.open.set(false); + } +} diff --git a/tedi/components/overlay/modal/modal.component.html b/tedi/components/overlay/modal/modal.component.html new file mode 100644 index 000000000..99e05205f --- /dev/null +++ b/tedi/components/overlay/modal/modal.component.html @@ -0,0 +1,15 @@ +@if (open()) { +
+ +} diff --git a/tedi/components/overlay/modal/modal.component.scss b/tedi/components/overlay/modal/modal.component.scss new file mode 100644 index 000000000..79d2c7a75 --- /dev/null +++ b/tedi/components/overlay/modal/modal.component.scss @@ -0,0 +1,129 @@ +@use "@tedi-design-system/core/typography"; + +@mixin modal-heading($size) { + .tedi-modal-header__head { + h1, + h2, + h3, + h4, + h5, + h6 { + @include typography.heading-styles($size); + } + } +} + +$modal-widths: (xs, sm, md, lg, xl); + +.tedi-modal { + position: fixed; + inset: 0; + display: none; + z-index: 1000; + + &--open { + display: block; + } + + &--default { + --_tedi-modal-heading-padding-x: var(--modal-heading-padding-x); + --_tedi-modal-heading-padding-y: var(--modal-heading-padding-y); + --_tedi-modal-body-padding: var(--modal-body-padding); + --_tedi-modal-footer-padding-x: var(--modal-footer-padding-x); + --_tedi-modal-footer-padding-y: var(--modal-footer-padding-y); + + @include modal-heading(h3); + } + + &--small { + --_tedi-modal-heading-padding-x: var(--modal-heading-padding-x-sm); + --_tedi-modal-heading-padding-y: var(--modal-heading-padding-y-sm); + --_tedi-modal-body-padding: var(--modal-body-padding-sm); + --_tedi-modal-footer-padding-x: var(--modal-footer-padding-x-sm); + --_tedi-modal-footer-padding-y: var(--modal-footer-padding-y-sm); + + @include modal-heading(h4); + } + + @each $width in $modal-widths { + &--#{$width} { + .tedi-modal__dialog { + max-width: var(--modal-max-width-#{$width}); + } + } + } + + &--center { + .tedi-modal__dialog { + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 95dvh; + } + } + + &--left { + .tedi-modal__dialog { + top: 0; + left: 0; + height: 100%; + } + } + + &--right { + .tedi-modal__dialog { + top: 0; + right: 0; + height: 100%; + } + } + + &__dialog { + position: fixed; + width: 100%; + display: flex; + flex-direction: column; + background-color: var(--modal-background); + border: var(--borders-01) solid var(--modal-border-outer); + border-radius: var(--modal-radius); + } + + &__backdrop { + position: fixed; + inset: 0; + background: var(--general-surface-overlay); + } + + tedi-modal-header { + border-bottom: var(--borders-01) solid var(--modal-border-inner); + padding: var(--_tedi-modal-heading-padding-y) + var(--_tedi-modal-heading-padding-x); + + .tedi-modal-header__head { + display: flex; + align-items: center; + gap: var(--layout-grid-gutters-08); + + button[tedi-closing-button] { + margin-left: auto; + } + } + } + + tedi-modal-content { + display: flex; + flex-direction: column; + gap: var(--layout-grid-gutters-16); + padding: var(--_tedi-modal-body-padding); + overflow-y: auto; + } + + tedi-modal-footer { + display: flex; + gap: var(--layout-grid-gutters-16); + + border-top: var(--borders-01) solid var(--modal-border-inner); + padding: var(--_tedi-modal-footer-padding-y) + var(--_tedi-modal-footer-padding-x); + } +} diff --git a/tedi/components/overlay/modal/modal.component.spec.ts b/tedi/components/overlay/modal/modal.component.spec.ts new file mode 100644 index 000000000..72adbe1b9 --- /dev/null +++ b/tedi/components/overlay/modal/modal.component.spec.ts @@ -0,0 +1,138 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ModalComponent } from "./modal.component"; +import { DOCUMENT } from "@angular/common"; +import { PLATFORM_ID } from "@angular/core"; + +describe("ModalComponent", () => { + let fixture: ComponentFixture; + let component: ModalComponent; + let hostEl: HTMLElement; + let documentRef: Document; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ModalComponent], + }); + + fixture = TestBed.createComponent(ModalComponent); + component = fixture.componentInstance; + hostEl = fixture.nativeElement; + documentRef = TestBed.inject(DOCUMENT); + + fixture.detectChanges(); + component.ngAfterViewInit(); + }); + + afterEach(() => { + component.ngOnDestroy(); + }); + + it("should create component", () => { + expect(component).toBeTruthy(); + }); + + it("should have default inputs", () => { + expect(component.size()).toBe("default"); + expect(component.width()).toBe("sm"); + expect(component.position()).toBe("center"); + expect(component.open()).toBe(false); + }); + + it("should apply correct default classes", () => { + const classes = hostEl.getAttribute("class")!; + expect(classes).toContain("tedi-modal--default"); + expect(classes).toContain("tedi-modal--sm"); + expect(classes).toContain("tedi-modal--center"); + expect(classes).not.toContain("tedi-modal--open"); + }); + + it("should add 'tedi-modal--open' class when opened", () => { + fixture.componentRef.setInput("open", true); + fixture.detectChanges(); + + const classes = hostEl.getAttribute("class")!; + expect(classes).toContain("tedi-modal--open"); + }); + + it("should lock body scroll when opened", () => { + fixture.componentRef.setInput("open", true); + fixture.detectChanges(); + + expect(documentRef.body.style.overflow).toBe("hidden"); + }); + + it("should restore body scroll when closed", () => { + fixture.componentRef.setInput("open", true); + fixture.detectChanges(); + + fixture.componentRef.setInput("open", false); + fixture.detectChanges(); + + expect(documentRef.body.style.overflow).toBe(""); + }); + + it("should close modal on Escape key", () => { + fixture.componentRef.setInput("open", true); + fixture.detectChanges(); + + const escEvent = new KeyboardEvent("keydown", { key: "Escape" }); + documentRef.dispatchEvent(escEvent); + + expect(component.open()).toBe(false); + }); + + it("should restore focus to previously focused element on close", () => { + const button = documentRef.createElement("button"); + documentRef.body.appendChild(button); + button.focus(); + + fixture.componentRef.setInput("open", true); + fixture.detectChanges(); + + fixture.componentRef.setInput("open", false); + fixture.detectChanges(); + + expect(documentRef.activeElement).toBe(button); + + button.remove(); + }); +}); + +describe("ModalComponent (server platform)", () => { + let fixture: ComponentFixture; + let component: ModalComponent; + let documentRef: Document; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ModalComponent], + providers: [{ provide: PLATFORM_ID, useValue: "server" }], + }); + + fixture = TestBed.createComponent(ModalComponent); + component = fixture.componentInstance; + documentRef = TestBed.inject(DOCUMENT); + + fixture.detectChanges(); + }); + + it("should NOT run browser-only effect in constructor", () => { + expect(documentRef.body.style.overflow).toBe(""); + }); + + it("should NOT append modal to body in ngAfterViewInit on server", () => { + const initialChildren = documentRef.body.childElementCount; + component.ngAfterViewInit(); + expect(documentRef.body.childElementCount).toBe(initialChildren); + }); + + it("should NOT remove element from body in ngOnDestroy on server", () => { + const el = fixture.nativeElement; + documentRef.body.appendChild(el); + + const initialChildren = documentRef.body.childElementCount; + component.ngOnDestroy(); + expect(documentRef.body.childElementCount).toBe(initialChildren); + el.remove(); + }); +}); diff --git a/tedi/components/overlay/modal/modal.component.ts b/tedi/components/overlay/modal/modal.component.ts new file mode 100644 index 000000000..41c36fb09 --- /dev/null +++ b/tedi/components/overlay/modal/modal.component.ts @@ -0,0 +1,121 @@ +import { + Component, + ViewEncapsulation, + ChangeDetectionStrategy, + input, + computed, + model, + inject, + AfterViewInit, + ElementRef, + OnDestroy, + PLATFORM_ID, + effect, +} from "@angular/core"; +import { DOCUMENT, isPlatformBrowser } from "@angular/common"; +import { CdkTrapFocus } from "@angular/cdk/a11y"; + +export type ModalSize = "default" | "small"; +export type ModalWidth = "xs" | "sm" | "md" | "lg" | "xl"; +export type ModalPosition = "center" | "left" | "right"; + +@Component({ + standalone: true, + selector: "tedi-modal", + imports: [CdkTrapFocus], + templateUrl: "./modal.component.html", + styleUrl: "./modal.component.scss", + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + "[class]": "classes()", + }, +}) +export class ModalComponent implements AfterViewInit, OnDestroy { + /** Is modal open? */ + readonly open = model(false); + + /** Modal size */ + readonly size = input("default"); + + /** Modal width */ + readonly width = input("sm"); + + /** Position of the modal */ + readonly position = input("center"); + + private readonly document = inject(DOCUMENT); + private readonly host = inject>(ElementRef); + private readonly platformId = inject(PLATFORM_ID); + + private prevBodyOverflow: string = ""; + private prevFocusedElement: HTMLElement | null = null; + + readonly classes = computed(() => { + const classList = [ + "tedi-modal", + `tedi-modal--${this.size()}`, + `tedi-modal--${this.width()}`, + `tedi-modal--${this.position()}`, + ]; + + if (this.open()) { + classList.push("tedi-modal--open"); + } + + return classList.join(" "); + }); + + constructor() { + effect(() => { + if (!isPlatformBrowser(this.platformId)) return; + + if (this.open()) { + this.onOpen(); + } else { + this.onClose(); + } + }); + } + + ngAfterViewInit(): void { + if (!isPlatformBrowser(this.platformId)) return; + + this.document.body.appendChild(this.host.nativeElement); + } + + ngOnDestroy() { + if (!isPlatformBrowser(this.platformId)) return; + + const element = this.host.nativeElement; + + if (element.parentNode) { + element.parentNode.removeChild(element); + } + + this.document.removeEventListener("keydown", this.handleKeydown); + } + + private onOpen() { + this.prevFocusedElement = this.document.activeElement as HTMLElement; + this.prevBodyOverflow = this.document.body.style.overflow; + this.document.body.style.overflow = "hidden"; + this.document.addEventListener("keydown", this.handleKeydown); + } + + private onClose() { + this.document.body.style.overflow = this.prevBodyOverflow; + + if (this.prevFocusedElement) { + this.prevFocusedElement.focus({ preventScroll: true }); + } + + this.document.removeEventListener("keydown", this.handleKeydown); + } + + private handleKeydown = (e: KeyboardEvent) => { + if (e.key === "Escape") { + this.open.set(false); + } + }; +} diff --git a/tedi/components/overlay/modal/modal.stories.ts b/tedi/components/overlay/modal/modal.stories.ts new file mode 100644 index 000000000..e8e91dd7f --- /dev/null +++ b/tedi/components/overlay/modal/modal.stories.ts @@ -0,0 +1,399 @@ +import { type Meta, type StoryObj, moduleMetadata } from "@storybook/angular"; +import { ModalComponent } from "./modal.component"; +import { ModalHeaderComponent } from "./modal-header/modal-header.component"; +import { ModalContentComponent } from "./modal-content/modal-content.component"; +import { ModalFooterComponent } from "./modal-footer/modal-footer.component"; +import { ButtonComponent } from "../../buttons/button/button.component"; +import { LabelComponent } from "../../form/label/label.component"; +import { SelectComponent } from "community/components/form/select/select.component"; +import { SelectOptionComponent } from "community/components/form/select/select-option.component"; +import { IconComponent } from "../../base/icon/icon.component"; + +/** + * Figma ↗
+ * Zeroheight ↗ + * + * --- + * + * The modal can be opened or closed using the `open` input (set it to `true` or `false`). + * You can also control it programmatically using `viewChild`: + * + * ```ts + * modal = viewChild(ModalComponent); + * + * toggleModal() { + * this.modal.open.update(prev => !prev); + * } + * ``` + * + * The modal layout is composed of the following subcomponents: + * + * - ModalHeaderComponent + * - ModalContentComponent + * - ModalFooterComponent + */ + +export default { + title: "TEDI-Ready/Components/Overlay/Modal", + component: ModalComponent, + decorators: [ + moduleMetadata({ + imports: [ + ModalComponent, + ModalHeaderComponent, + ModalContentComponent, + ModalFooterComponent, + ButtonComponent, + LabelComponent, + SelectComponent, + SelectOptionComponent, + IconComponent, + ], + }), + ], + argTypes: { + open: { + control: "boolean", + description: "Is modal open?", + table: { + category: "modal inputs", + type: { + summary: "boolean", + }, + defaultValue: { + summary: "false", + }, + }, + }, + size: { + control: "radio", + options: ["default", "small"], + description: "Modal size", + table: { + category: "modal inputs", + type: { + summary: "ModalSize", + detail: "default \nsmall", + }, + defaultValue: { + summary: "default", + }, + }, + }, + width: { + control: "radio", + options: ["xs", "sm", "md", "lg", "xl"], + description: "Modal width", + table: { + category: "modal inputs", + type: { + summary: "ModalWidth", + detail: "xs \nsm \nmd \nlg \nxl", + }, + defaultValue: { + summary: "sm", + }, + }, + }, + position: { + control: "radio", + options: ["center", "left", "right"], + description: "Position of the modal", + table: { + category: "modal inputs", + type: { + summary: "ModalPosition", + detail: "center \nleft \nright", + }, + defaultValue: { + summary: "center", + }, + }, + }, + showClose: { + control: "boolean", + description: "Should show closing button?", + table: { + category: "modal header inputs", + type: { + summary: "boolean", + }, + defaultValue: { + summary: "true", + }, + }, + }, + }, +} as Meta; + +type DefaultStory = StoryObj< + ModalComponent & { + showClose: boolean; + } +>; + +export const Default: DefaultStory = { + args: { + open: false, + size: "default", + width: "sm", + position: "center", + showClose: true, + }, + render: (args) => ({ + props: { + ...args, + options: [ + { value: "1", label: "Option 1" }, + { value: "2", label: "Option 2" }, + { value: "3", label: "Option 3" }, + { value: "4", label: "Option 4" }, + { value: "5", label: "Option 5" }, + ], + }, + template: ` + + + +

Title

+
+ +
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + + +
+ `, + }), +}; + +export const Size: StoryObj = { + render: (args) => ({ + props: { + ...args, + openDefault: false, + openSmall: false, + options: [ + { value: "1", label: "Option 1" }, + { value: "2", label: "Option 2" }, + { value: "3", label: "Option 3" }, + { value: "4", label: "Option 4" }, + { value: "5", label: "Option 5" }, + ], + }, + template: ` +
+ + +
+ + +

Title

+
+ +
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + + +
+ + +

Title

+
+ +
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + + +
+ `, + }), +}; + +export const FooterVariants: StoryObj = { + render: (args) => ({ + props: { + ...args, + openDefault: false, + openLeftRight: false, + openThreeButtons: false, + openNoFooter: false, + options: [ + { value: "1", label: "Option 1" }, + { value: "2", label: "Option 2" }, + { value: "3", label: "Option 3" }, + { value: "4", label: "Option 4" }, + { value: "5", label: "Option 5" }, + ], + }, + template: ` +
+ + + + +
+ + +

Title

+
+ +
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + + +
+ + +

Title

+
+ +
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + + +
+ + +

Title

+
+ +
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + @for (option of options; track option.value) { + + } + +
+
+ + +
+ + +
+
+
+ + +

Title

+
+ +
+ + + @for (option of options; track option.value) { + + } + +
+
+ + + @for (option of options; track option.value) { + + } + +
+
+
+ `, + }), +}; From 9e209200fa2efec857baabcea50d9e6928fa4558 Mon Sep 17 00:00:00 2001 From: Romet Pastak Date: Mon, 24 Nov 2025 09:12:39 +0200 Subject: [PATCH 3/3] Revert "feat(checkbox): merge rc into checkbox #74" This reverts commit b16f13b1d50ac76980de64105c0d97ab5dd84c6a. --- CHANGELOG.md | 12 - .../file-dropzone.component.html | 80 ++-- .../file-dropzone.component.scss | 4 - .../modal/footer/modal-footer.component.scss | 2 +- .../modal/header/modal-header.component.scss | 2 +- .../overlay/modal/modal.component.scss | 2 +- .../components/overlay/modal/modal.stories.ts | 5 - package-lock.json | 8 +- package.json | 2 +- tedi/components/overlay/index.ts | 3 +- tedi/components/overlay/modal/index.ts | 4 - .../modal-content.component.html | 1 - .../modal-content/modal-content.component.ts | 16 - .../modal-footer/modal-footer.component.ts | 15 - .../modal-header/modal-header.component.html | 7 - .../modal-header.component.spec.ts | 85 ---- .../modal-header/modal-header.component.ts | 29 -- .../overlay/modal/modal.component.html | 15 - .../overlay/modal/modal.component.scss | 129 ------ .../overlay/modal/modal.component.spec.ts | 138 ------ .../overlay/modal/modal.component.ts | 121 ------ .../components/overlay/modal/modal.stories.ts | 399 ------------------ 22 files changed, 48 insertions(+), 1031 deletions(-) delete mode 100644 tedi/components/overlay/modal/index.ts delete mode 100644 tedi/components/overlay/modal/modal-content/modal-content.component.html delete mode 100644 tedi/components/overlay/modal/modal-content/modal-content.component.ts delete mode 100644 tedi/components/overlay/modal/modal-footer/modal-footer.component.ts delete mode 100644 tedi/components/overlay/modal/modal-header/modal-header.component.html delete mode 100644 tedi/components/overlay/modal/modal-header/modal-header.component.spec.ts delete mode 100644 tedi/components/overlay/modal/modal-header/modal-header.component.ts delete mode 100644 tedi/components/overlay/modal/modal.component.html delete mode 100644 tedi/components/overlay/modal/modal.component.scss delete mode 100644 tedi/components/overlay/modal/modal.component.spec.ts delete mode 100644 tedi/components/overlay/modal/modal.component.ts delete mode 100644 tedi/components/overlay/modal/modal.stories.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ae03b4cfc..3748cc2d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,3 @@ -# [4.1.0-rc.1](https://github.com/TEDI-Design-System/angular/compare/angular-4.0.1-rc.2...angular-4.1.0-rc.1) (2025-11-14) - - -### Bug Fixes - -* **file-dropzone:** fix aligment of info button in file-dropzone [#186](https://github.com/TEDI-Design-System/angular/issues/186) ([bb94cb1](https://github.com/TEDI-Design-System/angular/commit/bb94cb14e411506deb0df6fd6272be5ea75a8572)) - - -### Features - -* **file-dropzone:** add ng-content for file list [#186](https://github.com/TEDI-Design-System/angular/issues/186) ([8dd9c81](https://github.com/TEDI-Design-System/angular/commit/8dd9c81224ae75360a979ec6ec2d47589bcef338)) - ## [4.0.1-rc.2](https://github.com/TEDI-Design-System/angular/compare/angular-4.0.1-rc.1...angular-4.0.1-rc.2) (2025-11-04) diff --git a/community/components/form/file-dropzone/file-dropzone.component.html b/community/components/form/file-dropzone/file-dropzone.component.html index c44064015..6e1aab398 100644 --- a/community/components/form/file-dropzone/file-dropzone.component.html +++ b/community/components/form/file-dropzone/file-dropzone.component.html @@ -32,49 +32,47 @@ }
- - @for (file of files(); track file.name) { -
- - -
- {{ file.label ?? file.name }} +@for (file of files(); track file.name) { +
+ + +
+ {{ file.label ?? file.name }} - @if (file && validateIndividually() && file.helper; as helper) { - - - - + @if (file && validateIndividually() && file.helper; as helper) { + + + + - - {{ helper.text }} - - - } -
- -
-
-
- } - + + {{ helper.text }} + + + } +
+ +
+
+
+} @if (this.uploadError(); as message) { diff --git a/community/components/form/file-dropzone/file-dropzone.component.scss b/community/components/form/file-dropzone/file-dropzone.component.scss index 1ea8c1f65..97fc1c6cb 100644 --- a/community/components/form/file-dropzone/file-dropzone.component.scss +++ b/community/components/form/file-dropzone/file-dropzone.component.scss @@ -17,7 +17,3 @@ .tedi-file-dropzone__tooltip--error .tedi-info-button { --button-main-neutral-text-default: var(--red-700); } - -.tedi-file-dropzone__file-name { - display: flex; -} diff --git a/community/components/overlay/modal/footer/modal-footer.component.scss b/community/components/overlay/modal/footer/modal-footer.component.scss index ae936e7d2..44c1ca059 100644 --- a/community/components/overlay/modal/footer/modal-footer.component.scss +++ b/community/components/overlay/modal/footer/modal-footer.component.scss @@ -4,7 +4,7 @@ --_modal-footer-gap: 0.5rem; display: flex; gap: var(--_modal-footer-gap); - border-top: var(--borders-01) solid var(--modal-border-inner); + border-top: var(--_modal-border); padding: var(--_tedi-modal-footer-padding-y) var(--_tedi-modal-footer-padding-x); diff --git a/community/components/overlay/modal/header/modal-header.component.scss b/community/components/overlay/modal/header/modal-header.component.scss index 29c7635a5..a5adcacc2 100644 --- a/community/components/overlay/modal/header/modal-header.component.scss +++ b/community/components/overlay/modal/header/modal-header.component.scss @@ -1,7 +1,7 @@ @use "@tedi-design-system/core/mixins"; .tedi-modal-header { - border-bottom: var(--borders-01) solid var(--modal-border-inner); + border-bottom: var(--_modal-border); padding: var(--_tedi-modal-heading-padding-y) var(--_tedi-modal-heading-padding-x); diff --git a/community/components/overlay/modal/modal.component.scss b/community/components/overlay/modal/modal.component.scss index ffd164257..7db1b677a 100644 --- a/community/components/overlay/modal/modal.component.scss +++ b/community/components/overlay/modal/modal.component.scss @@ -27,7 +27,7 @@ $modal-breapoints: (xs, sm, md, lg, xl); } .tedi-modal { - --_modal-border: var(--borders-01) solid var(--modal-border-outer); + --_modal-border: var(--borders-01) solid var(--modal-border); --_modal-padding: var(--dimensions-13); overflow: auto; diff --git a/community/components/overlay/modal/modal.stories.ts b/community/components/overlay/modal/modal.stories.ts index c4236c4bf..9bee2a71b 100644 --- a/community/components/overlay/modal/modal.stories.ts +++ b/community/components/overlay/modal/modal.stories.ts @@ -114,11 +114,6 @@ const meta: Meta = { imports: [ModalOpenComponent, StorybookModalComponent], }), ], - parameters: { - status: { - type: ["existsInTediReady"], - }, - }, argTypes: { maxWidth: { control: { diff --git a/package-lock.json b/package-lock.json index 71aeed9fb..d6b600d61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "@tedi-design-system/angular", "version": "0.0.0-semantic-version", "dependencies": { - "@tedi-design-system/core": "^2.4.0" + "@tedi-design-system/core": "^2.0.0" }, "devDependencies": { "@angular-devkit/core": "19.2.15", @@ -9423,9 +9423,9 @@ } }, "node_modules/@tedi-design-system/core": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tedi-design-system/core/-/core-2.4.0.tgz", - "integrity": "sha512-tLr2Yf/LwGDCBnaqO/Ar2XEYPpZkcBC/K42hxHReN+EY4BbQyzcbU1W8egQJlgfvHjaKSxXSsnZ8SNC0PMe3vA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tedi-design-system/core/-/core-2.0.0.tgz", + "integrity": "sha512-ODuJgRoQYxyvs702iphOuRd68vTBpmIEe/ol4rFssSVDAZ+pZOCbfd87qvoEHW+iDWDlsxHJIOrzMTl3qsTHAQ==", "engines": { "node": ">=18.0.0", "npm": ">=8.0.0" diff --git a/package.json b/package.json index 384f6cf20..d0436c693 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "ngx-float-ui": "^19.0.1 || ^20.0.0" }, "dependencies": { - "@tedi-design-system/core": "^2.4.0" + "@tedi-design-system/core": "^2.0.0" }, "devDependencies": { "@angular-devkit/core": "19.2.15", diff --git a/tedi/components/overlay/index.ts b/tedi/components/overlay/index.ts index 7b55370d8..7439217de 100644 --- a/tedi/components/overlay/index.ts +++ b/tedi/components/overlay/index.ts @@ -1,3 +1,2 @@ -export * from "./modal"; export * from "./tooltip"; -export * from "./popover"; +export * from "./popover"; \ No newline at end of file diff --git a/tedi/components/overlay/modal/index.ts b/tedi/components/overlay/modal/index.ts deleted file mode 100644 index c510b7b2b..000000000 --- a/tedi/components/overlay/modal/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./modal.component"; -export * from "./modal-content/modal-content.component"; -export * from "./modal-footer/modal-footer.component"; -export * from "./modal-header/modal-header.component"; diff --git a/tedi/components/overlay/modal/modal-content/modal-content.component.html b/tedi/components/overlay/modal/modal-content/modal-content.component.html deleted file mode 100644 index 40b372640..000000000 --- a/tedi/components/overlay/modal/modal-content/modal-content.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tedi/components/overlay/modal/modal-content/modal-content.component.ts b/tedi/components/overlay/modal/modal-content/modal-content.component.ts deleted file mode 100644 index 12679a1a3..000000000 --- a/tedi/components/overlay/modal/modal-content/modal-content.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { - Component, - ViewEncapsulation, - ChangeDetectionStrategy, -} from "@angular/core"; - -@Component({ - standalone: true, - selector: "tedi-modal-content", - imports: [], - templateUrl: "./modal-content.component.html", - styleUrl: "../modal.component.scss", - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ModalContentComponent {} diff --git a/tedi/components/overlay/modal/modal-footer/modal-footer.component.ts b/tedi/components/overlay/modal/modal-footer/modal-footer.component.ts deleted file mode 100644 index 89f2e9539..000000000 --- a/tedi/components/overlay/modal/modal-footer/modal-footer.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - Component, - ViewEncapsulation, - ChangeDetectionStrategy, -} from "@angular/core"; - -@Component({ - standalone: true, - selector: "tedi-modal-footer", - template: "", - styleUrl: "../modal.component.scss", - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ModalFooterComponent {} diff --git a/tedi/components/overlay/modal/modal-header/modal-header.component.html b/tedi/components/overlay/modal/modal-header/modal-header.component.html deleted file mode 100644 index 6898a52dc..000000000 --- a/tedi/components/overlay/modal/modal-header/modal-header.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
- - @if (showClose()) { - - } -
- diff --git a/tedi/components/overlay/modal/modal-header/modal-header.component.spec.ts b/tedi/components/overlay/modal/modal-header/modal-header.component.spec.ts deleted file mode 100644 index 06072a375..000000000 --- a/tedi/components/overlay/modal/modal-header/modal-header.component.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { Component } from "@angular/core"; -import { ModalHeaderComponent } from "./modal-header.component"; -import { ModalComponent } from "../modal.component"; -import { viewChild } from "@angular/core"; - -class MockModalComponent { - open = { - value: false, - set: jest.fn(function (val: boolean) { - this.value = val; - }), - }; -} - -@Component({ - standalone: true, - imports: [ModalHeaderComponent], - template: ` - - Header Content - - `, -}) -class TestHostComponent { - showClose = true; - header = viewChild.required(ModalHeaderComponent); -} - -describe("ModalHeaderComponent", () => { - let fixture: ComponentFixture; - let host: TestHostComponent; - let component: ModalHeaderComponent; - let modal: MockModalComponent; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [TestHostComponent], - providers: [{ provide: ModalComponent, useClass: MockModalComponent }], - }); - - fixture = TestBed.createComponent(TestHostComponent); - fixture.detectChanges(); - - host = fixture.componentInstance; - component = host.header(); - modal = TestBed.inject(ModalComponent) as unknown as MockModalComponent; - }); - - it("should create the component", () => { - expect(component).toBeTruthy(); - }); - - it("should show close button when showClose = true", () => { - const btn = fixture.nativeElement.querySelector("button"); - expect(btn).not.toBeNull(); - }); - - it("should hide close button when showClose = false", () => { - host.showClose = false; - fixture.detectChanges(); - - const btn = fixture.nativeElement.querySelector("button"); - expect(btn).toBeNull(); - }); - - it("should close modal when close button is clicked", () => { - const btn = fixture.nativeElement.querySelector("button")!; - modal.open.value = true; - - btn.click(); - fixture.detectChanges(); - - expect(modal.open.set).toHaveBeenCalledWith(false); - }); - - it("should close modal via closeModal() method", () => { - modal.open.value = true; - - component.closeModal(); - fixture.detectChanges(); - - expect(modal.open.set).toHaveBeenCalledWith(false); - }); -}); diff --git a/tedi/components/overlay/modal/modal-header/modal-header.component.ts b/tedi/components/overlay/modal/modal-header/modal-header.component.ts deleted file mode 100644 index bd98567fa..000000000 --- a/tedi/components/overlay/modal/modal-header/modal-header.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - Component, - ViewEncapsulation, - ChangeDetectionStrategy, - input, - inject, -} from "@angular/core"; -import { ClosingButtonComponent } from "../../../buttons/closing-button/closing-button.component"; -import { ModalComponent } from "../modal.component"; - -@Component({ - standalone: true, - selector: "tedi-modal-header", - imports: [ClosingButtonComponent], - templateUrl: "./modal-header.component.html", - styleUrl: "../modal.component.scss", - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ModalHeaderComponent { - /** Should show closing button? */ - readonly showClose = input(true); - - private readonly modal = inject(ModalComponent); - - closeModal() { - this.modal.open.set(false); - } -} diff --git a/tedi/components/overlay/modal/modal.component.html b/tedi/components/overlay/modal/modal.component.html deleted file mode 100644 index 99e05205f..000000000 --- a/tedi/components/overlay/modal/modal.component.html +++ /dev/null @@ -1,15 +0,0 @@ -@if (open()) { -
- -} diff --git a/tedi/components/overlay/modal/modal.component.scss b/tedi/components/overlay/modal/modal.component.scss deleted file mode 100644 index 79d2c7a75..000000000 --- a/tedi/components/overlay/modal/modal.component.scss +++ /dev/null @@ -1,129 +0,0 @@ -@use "@tedi-design-system/core/typography"; - -@mixin modal-heading($size) { - .tedi-modal-header__head { - h1, - h2, - h3, - h4, - h5, - h6 { - @include typography.heading-styles($size); - } - } -} - -$modal-widths: (xs, sm, md, lg, xl); - -.tedi-modal { - position: fixed; - inset: 0; - display: none; - z-index: 1000; - - &--open { - display: block; - } - - &--default { - --_tedi-modal-heading-padding-x: var(--modal-heading-padding-x); - --_tedi-modal-heading-padding-y: var(--modal-heading-padding-y); - --_tedi-modal-body-padding: var(--modal-body-padding); - --_tedi-modal-footer-padding-x: var(--modal-footer-padding-x); - --_tedi-modal-footer-padding-y: var(--modal-footer-padding-y); - - @include modal-heading(h3); - } - - &--small { - --_tedi-modal-heading-padding-x: var(--modal-heading-padding-x-sm); - --_tedi-modal-heading-padding-y: var(--modal-heading-padding-y-sm); - --_tedi-modal-body-padding: var(--modal-body-padding-sm); - --_tedi-modal-footer-padding-x: var(--modal-footer-padding-x-sm); - --_tedi-modal-footer-padding-y: var(--modal-footer-padding-y-sm); - - @include modal-heading(h4); - } - - @each $width in $modal-widths { - &--#{$width} { - .tedi-modal__dialog { - max-width: var(--modal-max-width-#{$width}); - } - } - } - - &--center { - .tedi-modal__dialog { - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - max-height: 95dvh; - } - } - - &--left { - .tedi-modal__dialog { - top: 0; - left: 0; - height: 100%; - } - } - - &--right { - .tedi-modal__dialog { - top: 0; - right: 0; - height: 100%; - } - } - - &__dialog { - position: fixed; - width: 100%; - display: flex; - flex-direction: column; - background-color: var(--modal-background); - border: var(--borders-01) solid var(--modal-border-outer); - border-radius: var(--modal-radius); - } - - &__backdrop { - position: fixed; - inset: 0; - background: var(--general-surface-overlay); - } - - tedi-modal-header { - border-bottom: var(--borders-01) solid var(--modal-border-inner); - padding: var(--_tedi-modal-heading-padding-y) - var(--_tedi-modal-heading-padding-x); - - .tedi-modal-header__head { - display: flex; - align-items: center; - gap: var(--layout-grid-gutters-08); - - button[tedi-closing-button] { - margin-left: auto; - } - } - } - - tedi-modal-content { - display: flex; - flex-direction: column; - gap: var(--layout-grid-gutters-16); - padding: var(--_tedi-modal-body-padding); - overflow-y: auto; - } - - tedi-modal-footer { - display: flex; - gap: var(--layout-grid-gutters-16); - - border-top: var(--borders-01) solid var(--modal-border-inner); - padding: var(--_tedi-modal-footer-padding-y) - var(--_tedi-modal-footer-padding-x); - } -} diff --git a/tedi/components/overlay/modal/modal.component.spec.ts b/tedi/components/overlay/modal/modal.component.spec.ts deleted file mode 100644 index 72adbe1b9..000000000 --- a/tedi/components/overlay/modal/modal.component.spec.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { ModalComponent } from "./modal.component"; -import { DOCUMENT } from "@angular/common"; -import { PLATFORM_ID } from "@angular/core"; - -describe("ModalComponent", () => { - let fixture: ComponentFixture; - let component: ModalComponent; - let hostEl: HTMLElement; - let documentRef: Document; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ModalComponent], - }); - - fixture = TestBed.createComponent(ModalComponent); - component = fixture.componentInstance; - hostEl = fixture.nativeElement; - documentRef = TestBed.inject(DOCUMENT); - - fixture.detectChanges(); - component.ngAfterViewInit(); - }); - - afterEach(() => { - component.ngOnDestroy(); - }); - - it("should create component", () => { - expect(component).toBeTruthy(); - }); - - it("should have default inputs", () => { - expect(component.size()).toBe("default"); - expect(component.width()).toBe("sm"); - expect(component.position()).toBe("center"); - expect(component.open()).toBe(false); - }); - - it("should apply correct default classes", () => { - const classes = hostEl.getAttribute("class")!; - expect(classes).toContain("tedi-modal--default"); - expect(classes).toContain("tedi-modal--sm"); - expect(classes).toContain("tedi-modal--center"); - expect(classes).not.toContain("tedi-modal--open"); - }); - - it("should add 'tedi-modal--open' class when opened", () => { - fixture.componentRef.setInput("open", true); - fixture.detectChanges(); - - const classes = hostEl.getAttribute("class")!; - expect(classes).toContain("tedi-modal--open"); - }); - - it("should lock body scroll when opened", () => { - fixture.componentRef.setInput("open", true); - fixture.detectChanges(); - - expect(documentRef.body.style.overflow).toBe("hidden"); - }); - - it("should restore body scroll when closed", () => { - fixture.componentRef.setInput("open", true); - fixture.detectChanges(); - - fixture.componentRef.setInput("open", false); - fixture.detectChanges(); - - expect(documentRef.body.style.overflow).toBe(""); - }); - - it("should close modal on Escape key", () => { - fixture.componentRef.setInput("open", true); - fixture.detectChanges(); - - const escEvent = new KeyboardEvent("keydown", { key: "Escape" }); - documentRef.dispatchEvent(escEvent); - - expect(component.open()).toBe(false); - }); - - it("should restore focus to previously focused element on close", () => { - const button = documentRef.createElement("button"); - documentRef.body.appendChild(button); - button.focus(); - - fixture.componentRef.setInput("open", true); - fixture.detectChanges(); - - fixture.componentRef.setInput("open", false); - fixture.detectChanges(); - - expect(documentRef.activeElement).toBe(button); - - button.remove(); - }); -}); - -describe("ModalComponent (server platform)", () => { - let fixture: ComponentFixture; - let component: ModalComponent; - let documentRef: Document; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ModalComponent], - providers: [{ provide: PLATFORM_ID, useValue: "server" }], - }); - - fixture = TestBed.createComponent(ModalComponent); - component = fixture.componentInstance; - documentRef = TestBed.inject(DOCUMENT); - - fixture.detectChanges(); - }); - - it("should NOT run browser-only effect in constructor", () => { - expect(documentRef.body.style.overflow).toBe(""); - }); - - it("should NOT append modal to body in ngAfterViewInit on server", () => { - const initialChildren = documentRef.body.childElementCount; - component.ngAfterViewInit(); - expect(documentRef.body.childElementCount).toBe(initialChildren); - }); - - it("should NOT remove element from body in ngOnDestroy on server", () => { - const el = fixture.nativeElement; - documentRef.body.appendChild(el); - - const initialChildren = documentRef.body.childElementCount; - component.ngOnDestroy(); - expect(documentRef.body.childElementCount).toBe(initialChildren); - el.remove(); - }); -}); diff --git a/tedi/components/overlay/modal/modal.component.ts b/tedi/components/overlay/modal/modal.component.ts deleted file mode 100644 index 41c36fb09..000000000 --- a/tedi/components/overlay/modal/modal.component.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { - Component, - ViewEncapsulation, - ChangeDetectionStrategy, - input, - computed, - model, - inject, - AfterViewInit, - ElementRef, - OnDestroy, - PLATFORM_ID, - effect, -} from "@angular/core"; -import { DOCUMENT, isPlatformBrowser } from "@angular/common"; -import { CdkTrapFocus } from "@angular/cdk/a11y"; - -export type ModalSize = "default" | "small"; -export type ModalWidth = "xs" | "sm" | "md" | "lg" | "xl"; -export type ModalPosition = "center" | "left" | "right"; - -@Component({ - standalone: true, - selector: "tedi-modal", - imports: [CdkTrapFocus], - templateUrl: "./modal.component.html", - styleUrl: "./modal.component.scss", - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - host: { - "[class]": "classes()", - }, -}) -export class ModalComponent implements AfterViewInit, OnDestroy { - /** Is modal open? */ - readonly open = model(false); - - /** Modal size */ - readonly size = input("default"); - - /** Modal width */ - readonly width = input("sm"); - - /** Position of the modal */ - readonly position = input("center"); - - private readonly document = inject(DOCUMENT); - private readonly host = inject>(ElementRef); - private readonly platformId = inject(PLATFORM_ID); - - private prevBodyOverflow: string = ""; - private prevFocusedElement: HTMLElement | null = null; - - readonly classes = computed(() => { - const classList = [ - "tedi-modal", - `tedi-modal--${this.size()}`, - `tedi-modal--${this.width()}`, - `tedi-modal--${this.position()}`, - ]; - - if (this.open()) { - classList.push("tedi-modal--open"); - } - - return classList.join(" "); - }); - - constructor() { - effect(() => { - if (!isPlatformBrowser(this.platformId)) return; - - if (this.open()) { - this.onOpen(); - } else { - this.onClose(); - } - }); - } - - ngAfterViewInit(): void { - if (!isPlatformBrowser(this.platformId)) return; - - this.document.body.appendChild(this.host.nativeElement); - } - - ngOnDestroy() { - if (!isPlatformBrowser(this.platformId)) return; - - const element = this.host.nativeElement; - - if (element.parentNode) { - element.parentNode.removeChild(element); - } - - this.document.removeEventListener("keydown", this.handleKeydown); - } - - private onOpen() { - this.prevFocusedElement = this.document.activeElement as HTMLElement; - this.prevBodyOverflow = this.document.body.style.overflow; - this.document.body.style.overflow = "hidden"; - this.document.addEventListener("keydown", this.handleKeydown); - } - - private onClose() { - this.document.body.style.overflow = this.prevBodyOverflow; - - if (this.prevFocusedElement) { - this.prevFocusedElement.focus({ preventScroll: true }); - } - - this.document.removeEventListener("keydown", this.handleKeydown); - } - - private handleKeydown = (e: KeyboardEvent) => { - if (e.key === "Escape") { - this.open.set(false); - } - }; -} diff --git a/tedi/components/overlay/modal/modal.stories.ts b/tedi/components/overlay/modal/modal.stories.ts deleted file mode 100644 index e8e91dd7f..000000000 --- a/tedi/components/overlay/modal/modal.stories.ts +++ /dev/null @@ -1,399 +0,0 @@ -import { type Meta, type StoryObj, moduleMetadata } from "@storybook/angular"; -import { ModalComponent } from "./modal.component"; -import { ModalHeaderComponent } from "./modal-header/modal-header.component"; -import { ModalContentComponent } from "./modal-content/modal-content.component"; -import { ModalFooterComponent } from "./modal-footer/modal-footer.component"; -import { ButtonComponent } from "../../buttons/button/button.component"; -import { LabelComponent } from "../../form/label/label.component"; -import { SelectComponent } from "community/components/form/select/select.component"; -import { SelectOptionComponent } from "community/components/form/select/select-option.component"; -import { IconComponent } from "../../base/icon/icon.component"; - -/** - * Figma ↗
- * Zeroheight ↗ - * - * --- - * - * The modal can be opened or closed using the `open` input (set it to `true` or `false`). - * You can also control it programmatically using `viewChild`: - * - * ```ts - * modal = viewChild(ModalComponent); - * - * toggleModal() { - * this.modal.open.update(prev => !prev); - * } - * ``` - * - * The modal layout is composed of the following subcomponents: - * - * - ModalHeaderComponent - * - ModalContentComponent - * - ModalFooterComponent - */ - -export default { - title: "TEDI-Ready/Components/Overlay/Modal", - component: ModalComponent, - decorators: [ - moduleMetadata({ - imports: [ - ModalComponent, - ModalHeaderComponent, - ModalContentComponent, - ModalFooterComponent, - ButtonComponent, - LabelComponent, - SelectComponent, - SelectOptionComponent, - IconComponent, - ], - }), - ], - argTypes: { - open: { - control: "boolean", - description: "Is modal open?", - table: { - category: "modal inputs", - type: { - summary: "boolean", - }, - defaultValue: { - summary: "false", - }, - }, - }, - size: { - control: "radio", - options: ["default", "small"], - description: "Modal size", - table: { - category: "modal inputs", - type: { - summary: "ModalSize", - detail: "default \nsmall", - }, - defaultValue: { - summary: "default", - }, - }, - }, - width: { - control: "radio", - options: ["xs", "sm", "md", "lg", "xl"], - description: "Modal width", - table: { - category: "modal inputs", - type: { - summary: "ModalWidth", - detail: "xs \nsm \nmd \nlg \nxl", - }, - defaultValue: { - summary: "sm", - }, - }, - }, - position: { - control: "radio", - options: ["center", "left", "right"], - description: "Position of the modal", - table: { - category: "modal inputs", - type: { - summary: "ModalPosition", - detail: "center \nleft \nright", - }, - defaultValue: { - summary: "center", - }, - }, - }, - showClose: { - control: "boolean", - description: "Should show closing button?", - table: { - category: "modal header inputs", - type: { - summary: "boolean", - }, - defaultValue: { - summary: "true", - }, - }, - }, - }, -} as Meta; - -type DefaultStory = StoryObj< - ModalComponent & { - showClose: boolean; - } ->; - -export const Default: DefaultStory = { - args: { - open: false, - size: "default", - width: "sm", - position: "center", - showClose: true, - }, - render: (args) => ({ - props: { - ...args, - options: [ - { value: "1", label: "Option 1" }, - { value: "2", label: "Option 2" }, - { value: "3", label: "Option 3" }, - { value: "4", label: "Option 4" }, - { value: "5", label: "Option 5" }, - ], - }, - template: ` - - - -

Title

-
- -
- - - @for (option of options; track option.value) { - - } - -
-
- - - @for (option of options; track option.value) { - - } - -
-
- - - - -
- `, - }), -}; - -export const Size: StoryObj = { - render: (args) => ({ - props: { - ...args, - openDefault: false, - openSmall: false, - options: [ - { value: "1", label: "Option 1" }, - { value: "2", label: "Option 2" }, - { value: "3", label: "Option 3" }, - { value: "4", label: "Option 4" }, - { value: "5", label: "Option 5" }, - ], - }, - template: ` -
- - -
- - -

Title

-
- -
- - - @for (option of options; track option.value) { - - } - -
-
- - - @for (option of options; track option.value) { - - } - -
-
- - - - -
- - -

Title

-
- -
- - - @for (option of options; track option.value) { - - } - -
-
- - - @for (option of options; track option.value) { - - } - -
-
- - - - -
- `, - }), -}; - -export const FooterVariants: StoryObj = { - render: (args) => ({ - props: { - ...args, - openDefault: false, - openLeftRight: false, - openThreeButtons: false, - openNoFooter: false, - options: [ - { value: "1", label: "Option 1" }, - { value: "2", label: "Option 2" }, - { value: "3", label: "Option 3" }, - { value: "4", label: "Option 4" }, - { value: "5", label: "Option 5" }, - ], - }, - template: ` -
- - - - -
- - -

Title

-
- -
- - - @for (option of options; track option.value) { - - } - -
-
- - - @for (option of options; track option.value) { - - } - -
-
- - - - -
- - -

Title

-
- -
- - - @for (option of options; track option.value) { - - } - -
-
- - - @for (option of options; track option.value) { - - } - -
-
- - - - -
- - -

Title

-
- -
- - - @for (option of options; track option.value) { - - } - -
-
- - - @for (option of options; track option.value) { - - } - -
-
- - -
- - -
-
-
- - -

Title

-
- -
- - - @for (option of options; track option.value) { - - } - -
-
- - - @for (option of options; track option.value) { - - } - -
-
-
- `, - }), -};