From 7a8aaa8c7ca0b3b88862624fe0f28fe553b7d651 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Tue, 11 Nov 2025 17:56:47 +0200 Subject: [PATCH 01/23] feat(floating-button): initial commit - create floating-button component #195 --- .../floating-button.component.scss | 95 +++++++++++++++++++ .../floating-button.component.ts | 68 +++++++++++++ .../floating-button.stories.ts | 24 +++++ 3 files changed, 187 insertions(+) create mode 100644 community/components/overlay/floating-button/floating-button.component.scss create mode 100644 community/components/overlay/floating-button/floating-button.component.ts create mode 100644 community/components/overlay/floating-button/floating-button.stories.ts diff --git a/community/components/overlay/floating-button/floating-button.component.scss b/community/components/overlay/floating-button/floating-button.component.scss new file mode 100644 index 000000000..d8181f085 --- /dev/null +++ b/community/components/overlay/floating-button/floating-button.component.scss @@ -0,0 +1,95 @@ +@use '@tedi-design-system/core/mixins'; +@use '@tedi-design-system/core/bootstrap-utility/breakpoints'; +@use '../button-content/button-content.module'; + +$btn-height: 2.75rem; +$btn-height-small: 2.5rem; +$btn-height-large: 3rem; + +.tedi-floating-button { + box-sizing: border-box; + text-wrap: wrap; + box-shadow: 0 4px 10px 0 var(--alpha-14); + + &--horizontal { + @include mixins.responsive-styles(border-radius, button-radius-default); + } + + &--vertical { + min-width: max-content; + white-space: nowrap; + border-radius: 0; + transform: rotate(-90deg); + transform-origin: center; + + @include mixins.responsive-styles(border-top-right-radius, button-radius-sm); + @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); + } + + &.tedi-floating-button--primary { + @include button-content.button-variant( + var(--button-floating-primary-background-default), + var(--button-floating-primary-border-default), + var(--button-floating-primary-text-default), + var(--button-floating-primary-background-hover), + var(--button-floating-primary-border-hover), + var(--button-floating-primary-text-default), + var(--button-floating-primary-border-focus), + var(--button-floating-primary-background-focus), + var(--button-floating-primary-text-default), + var(--button-floating-primary-border-active), + var(--button-floating-primary-background-active) + ); + } + + &.tedi-floating-button--secondary { + @include button-content.button-variant( + var(--button-floating-secondary-background-default), + var(--button-floating-secondary-border-default), + var(--button-floating-secondary-text-default), + var(--button-floating-secondary-background-hover), + var(--button-floating-secondary-border-hover), + var(--button-floating-secondary-text-hover), + var(--button-floating-primary-border-focus), + var(--button-floating-secondary-background-focus), + var(--button-floating-secondary-text-active), + var(--button-floating-secondary-border-active), + var(--button-floating-secondary-background-active) + ); + } + + &--medium { + height: auto; + min-height: $btn-height; + + @include breakpoints.media-breakpoint-up(md) { + min-height: $btn-height-small; + } + + @include mixins.responsive-styles(padding, button-md-padding-y button-md-padding-x); + + &.tedi-floating-button--icon-only { + @include mixins.responsive-styles(width, button-md-icon-size); + @include mixins.responsive-styles(height, button-md-icon-size); + @include mixins.responsive-styles(padding, button-md-icon-padding); + } + } + + &--large { + height: auto; + min-height: $btn-height-large; + + @include mixins.responsive-styles(padding, button-xl-padding-y button-xl-padding-x); + + &.tedi-floating-button--icon-only { + @include mixins.responsive-styles(width, button-xl-icon-size); + @include mixins.responsive-styles(height, button-xl-icon-size); + @include mixins.responsive-styles(padding, button-xl-icon-padding); + } + } + + &:not(&--icon-only) { + @include mixins.responsive-styles(min-width, button-width-min); + @include mixins.responsive-styles(max-width, button-width-max); + } +} diff --git a/community/components/overlay/floating-button/floating-button.component.ts b/community/components/overlay/floating-button/floating-button.component.ts new file mode 100644 index 000000000..bf2cc9a33 --- /dev/null +++ b/community/components/overlay/floating-button/floating-button.component.ts @@ -0,0 +1,68 @@ +import { Component, computed, input } from "@angular/core"; + +export type FloatingButtonAxis = "horizontal" | "vertical"; +export type FloatingButtonColor = "primary" | "secondary"; +export type FloatingButtonSize = "medium" | "large"; +export type FloatingButtonPlacement = { + vertical: "top" | "bottom" | "center"; + horizontal: "left" | "right" | "center"; +}; +export type FloatingButtonOffset = { + top?: number | string; + bottom?: number | string; + left?: number | string; + right?: number | string; +}; + +@Component({ + selector: "[tedi-floating-button]", + template: "", + styleUrl: "./floating-button.component.scss", + host: { + "[class]": "classes()", + }, +}) +export class FloatingButtonComponent { + /** + * Button axis + * @default horizontal + */ + axis = input("horizontal"); + /** + * Button visual type + * @default primary + */ + visualType = input(); + /** + * Button size + * @default medium + */ + size?: FloatingButtonSize; + /** + * Button position + * @default fixed + */ + position = input("fixed"); + /** + * Button placement + */ + placement?: FloatingButtonPlacement; + /** + * Button offset + */ + offset?: FloatingButtonOffset; + + classes = computed(() => { + const classes = ["tedi-floating-button"]; + if (this.axis()) { + classes.push(`tedi-floating-button--axis--${this.axis()}`); + } + if (this.visualType()) { + classes.push(`tedi-floating-button--visual-type--${this.visualType()}`); + } + if (this.size) { + classes.push(`tedi-floating-button--size--${this.size}`); + } + return classes.join(" "); + }); +} diff --git a/community/components/overlay/floating-button/floating-button.stories.ts b/community/components/overlay/floating-button/floating-button.stories.ts new file mode 100644 index 000000000..db37dbb4a --- /dev/null +++ b/community/components/overlay/floating-button/floating-button.stories.ts @@ -0,0 +1,24 @@ +import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { FloatingButtonComponent } from "./floating-button.component"; +import { ButtonComponent } from "tedi/components"; + +const meta: Meta = { + title: "Components/Overlay/Floating Button", + component: FloatingButtonComponent, + + decorators: [ + moduleMetadata({ + imports: [FloatingButtonComponent, ButtonComponent], + }), + ], +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => ({ + template: ``, + }), +}; From d9d6a07b6f6014d5795534186e90a300cfde8565 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:32:21 +0200 Subject: [PATCH 02/23] feat(floating-button): prepare storybook + move styles to button.component.css #195 --- .../floating-button.component.scss | 95 ---------------- .../floating-button.component.ts | 30 +++--- .../floating-button.stories.ts | 65 ++++++++++- .../buttons/button/button.component.scss | 102 ++++++++++++++++++ 4 files changed, 180 insertions(+), 112 deletions(-) delete mode 100644 community/components/overlay/floating-button/floating-button.component.scss diff --git a/community/components/overlay/floating-button/floating-button.component.scss b/community/components/overlay/floating-button/floating-button.component.scss deleted file mode 100644 index d8181f085..000000000 --- a/community/components/overlay/floating-button/floating-button.component.scss +++ /dev/null @@ -1,95 +0,0 @@ -@use '@tedi-design-system/core/mixins'; -@use '@tedi-design-system/core/bootstrap-utility/breakpoints'; -@use '../button-content/button-content.module'; - -$btn-height: 2.75rem; -$btn-height-small: 2.5rem; -$btn-height-large: 3rem; - -.tedi-floating-button { - box-sizing: border-box; - text-wrap: wrap; - box-shadow: 0 4px 10px 0 var(--alpha-14); - - &--horizontal { - @include mixins.responsive-styles(border-radius, button-radius-default); - } - - &--vertical { - min-width: max-content; - white-space: nowrap; - border-radius: 0; - transform: rotate(-90deg); - transform-origin: center; - - @include mixins.responsive-styles(border-top-right-radius, button-radius-sm); - @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); - } - - &.tedi-floating-button--primary { - @include button-content.button-variant( - var(--button-floating-primary-background-default), - var(--button-floating-primary-border-default), - var(--button-floating-primary-text-default), - var(--button-floating-primary-background-hover), - var(--button-floating-primary-border-hover), - var(--button-floating-primary-text-default), - var(--button-floating-primary-border-focus), - var(--button-floating-primary-background-focus), - var(--button-floating-primary-text-default), - var(--button-floating-primary-border-active), - var(--button-floating-primary-background-active) - ); - } - - &.tedi-floating-button--secondary { - @include button-content.button-variant( - var(--button-floating-secondary-background-default), - var(--button-floating-secondary-border-default), - var(--button-floating-secondary-text-default), - var(--button-floating-secondary-background-hover), - var(--button-floating-secondary-border-hover), - var(--button-floating-secondary-text-hover), - var(--button-floating-primary-border-focus), - var(--button-floating-secondary-background-focus), - var(--button-floating-secondary-text-active), - var(--button-floating-secondary-border-active), - var(--button-floating-secondary-background-active) - ); - } - - &--medium { - height: auto; - min-height: $btn-height; - - @include breakpoints.media-breakpoint-up(md) { - min-height: $btn-height-small; - } - - @include mixins.responsive-styles(padding, button-md-padding-y button-md-padding-x); - - &.tedi-floating-button--icon-only { - @include mixins.responsive-styles(width, button-md-icon-size); - @include mixins.responsive-styles(height, button-md-icon-size); - @include mixins.responsive-styles(padding, button-md-icon-padding); - } - } - - &--large { - height: auto; - min-height: $btn-height-large; - - @include mixins.responsive-styles(padding, button-xl-padding-y button-xl-padding-x); - - &.tedi-floating-button--icon-only { - @include mixins.responsive-styles(width, button-xl-icon-size); - @include mixins.responsive-styles(height, button-xl-icon-size); - @include mixins.responsive-styles(padding, button-xl-icon-padding); - } - } - - &:not(&--icon-only) { - @include mixins.responsive-styles(min-width, button-width-min); - @include mixins.responsive-styles(max-width, button-width-max); - } -} diff --git a/community/components/overlay/floating-button/floating-button.component.ts b/community/components/overlay/floating-button/floating-button.component.ts index bf2cc9a33..222234fd2 100644 --- a/community/components/overlay/floating-button/floating-button.component.ts +++ b/community/components/overlay/floating-button/floating-button.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, input } from "@angular/core"; +import { computed, Directive, input } from "@angular/core"; export type FloatingButtonAxis = "horizontal" | "vertical"; export type FloatingButtonColor = "primary" | "secondary"; @@ -14,15 +14,13 @@ export type FloatingButtonOffset = { right?: number | string; }; -@Component({ +@Directive({ selector: "[tedi-floating-button]", - template: "", - styleUrl: "./floating-button.component.scss", host: { "[class]": "classes()", }, }) -export class FloatingButtonComponent { +export class FloatingButtonDirective { /** * Button axis * @default horizontal @@ -37,31 +35,39 @@ export class FloatingButtonComponent { * Button size * @default medium */ - size?: FloatingButtonSize; + size = input("medium"); /** - * Button position + * Button positionwu * @default fixed */ position = input("fixed"); /** * Button placement + * @default { vertical: 'bottom', horizontal: 'right' } */ - placement?: FloatingButtonPlacement; + placement = input({ + vertical: "bottom", + horizontal: "right", + }); /** * Button offset + * @default { bottom: '1.5rem', right: '1.5rem' } */ - offset?: FloatingButtonOffset; + offset = input({ + bottom: "1.5rem", + right: "1.5rem", + }); classes = computed(() => { const classes = ["tedi-floating-button"]; if (this.axis()) { - classes.push(`tedi-floating-button--axis--${this.axis()}`); + classes.push(`tedi-floating-button--${this.axis()}`); } if (this.visualType()) { - classes.push(`tedi-floating-button--visual-type--${this.visualType()}`); + classes.push(`tedi-floating-button--${this.visualType()}`); } if (this.size) { - classes.push(`tedi-floating-button--size--${this.size}`); + classes.push(`tedi-floating-button--${this.size()}`); } return classes.join(" "); }); diff --git a/community/components/overlay/floating-button/floating-button.stories.ts b/community/components/overlay/floating-button/floating-button.stories.ts index db37dbb4a..e060ad342 100644 --- a/community/components/overlay/floating-button/floating-button.stories.ts +++ b/community/components/overlay/floating-button/floating-button.stories.ts @@ -1,24 +1,79 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; -import { FloatingButtonComponent } from "./floating-button.component"; +import { FloatingButtonDirective } from "./floating-button.component"; import { ButtonComponent } from "tedi/components"; +import { NgFor } from "@angular/common"; -const meta: Meta = { +const buttonSizeArray = ["medium", "large"]; +const buttonStateArray = ["Default", "Hover", "Active", "Focus"]; + +const meta: Meta = { title: "Components/Overlay/Floating Button", - component: FloatingButtonComponent, + component: FloatingButtonDirective, decorators: [ moduleMetadata({ - imports: [FloatingButtonComponent, ButtonComponent], + imports: [FloatingButtonDirective, ButtonComponent, NgFor], }), ], }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = { + parameters: { + pseudo: { + hover: "#Hover", + active: "#Active", + focusVisible: "#Focus", + }, + }, render: () => ({ template: ``, }), }; + +export const sizesVertical: Story = { + ...Default, + render: (args) => ({ + props: { ...args, buttonSizeArray }, + template: ` + @for(size of buttonSizeArray; track size) { + + }`, + }), +}; + +export const statesVertical: Story = { + ...Default, + render: (args) => ({ + props: { ...args, buttonStateArray }, + template: ` + @for(state of buttonStateArray; track state) { + + }`, + }), +}; + +export const sizesHorizontal: Story = { + ...Default, + render: (args) => ({ + props: { ...args, buttonSizeArray }, + template: ` + @for(size of buttonSizeArray; track size) { + + }`, + }), +}; + +export const statesHorizontal: Story = { + ...Default, + render: (args) => ({ + props: { ...args, buttonStateArray }, + template: ` + @for(state of buttonStateArray; track state) { + + }`, + }), +}; diff --git a/tedi/components/buttons/button/button.component.scss b/tedi/components/buttons/button/button.component.scss index 9bc60658e..4efdeac2c 100644 --- a/tedi/components/buttons/button/button.component.scss +++ b/tedi/components/buttons/button/button.component.scss @@ -1,4 +1,5 @@ @use "@tedi-design-system/core/mixins"; +@use "@tedi-design-system/core/bootstrap-utility/breakpoints"; @use "sass:list"; $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; @@ -251,3 +252,104 @@ $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; font-size: inherit; } } + +$btn-height: 2.75rem; +$btn-height-small: 2.5rem; +$btn-height-large: 3rem; + +.tedi-floating-button { + box-sizing: border-box; + text-wrap: wrap; + box-shadow: 0 4px 10px 0 var(--alpha-14); + + &--horizontal { + @include mixins.responsive-styles(border-radius, button-radius-default); + } + + &--vertical { + min-width: max-content; + white-space: nowrap; + border-radius: 0; + transform: rotate(-90deg); + transform-origin: center; + + @include mixins.responsive-styles( + border-top-right-radius, + button-radius-sm + ); + @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); + } + + // &.tedi-floating-button--primary { + // @include button-content.button-variant( + // var(--button-floating-primary-background-default), + // var(--button-floating-primary-border-default), + // var(--button-floating-primary-text-default), + // var(--button-floating-primary-background-hover), + // var(--button-floating-primary-border-hover), + // var(--button-floating-primary-text-default), + // var(--button-floating-primary-border-focus), + // var(--button-floating-primary-background-focus), + // var(--button-floating-primary-text-default), + // var(--button-floating-primary-border-active), + // var(--button-floating-primary-background-active) + // ); + // } + + // &.tedi-floating-button--secondary { + // @include button-content.button-variant( + // var(--button-floating-secondary-background-default), + // var(--button-floating-secondary-border-default), + // var(--button-floating-secondary-text-default), + // var(--button-floating-secondary-background-hover), + // var(--button-floating-secondary-border-hover), + // var(--button-floating-secondary-text-hover), + // var(--button-floating-primary-border-focus), + // var(--button-floating-secondary-background-focus), + // var(--button-floating-secondary-text-active), + // var(--button-floating-secondary-border-active), + // var(--button-floating-secondary-background-active) + // ); + // } + + &--medium { + height: auto; + min-height: $btn-height; + + @include breakpoints.media-breakpoint-up(md) { + min-height: $btn-height-small; + } + + @include mixins.responsive-styles( + padding, + button-md-padding-y button-md-padding-x + ); + + &.tedi-floating-button--icon-only { + @include mixins.responsive-styles(width, button-md-icon-size); + @include mixins.responsive-styles(height, button-md-icon-size); + @include mixins.responsive-styles(padding, button-md-icon-padding); + } + } + + &--large { + height: auto; + min-height: $btn-height-large; + + @include mixins.responsive-styles( + padding, + button-xl-padding-y button-xl-padding-x + ); + + &.tedi-floating-button--icon-only { + @include mixins.responsive-styles(width, button-xl-icon-size); + @include mixins.responsive-styles(height, button-xl-icon-size); + @include mixins.responsive-styles(padding, button-xl-icon-padding); + } + } + + &:not(&--icon-only) { + @include mixins.responsive-styles(min-width, button-width-min); + @include mixins.responsive-styles(max-width, button-width-max); + } +} From bb12938a8f58e66f47c2c8d031ec657f9c4aab2c Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:52:49 +0200 Subject: [PATCH 03/23] feat(floating-button): move to correct folder #195 --- .../floating-button.component.ts | 37 +++++++++- .../floating-button.stories.ts | 2 +- .../buttons/button/button.component.scss | 69 ++++++++++--------- 3 files changed, 75 insertions(+), 33 deletions(-) rename community/components/{overlay => buttons}/floating-button/floating-button.component.ts (59%) rename community/components/{overlay => buttons}/floating-button/floating-button.stories.ts (97%) diff --git a/community/components/overlay/floating-button/floating-button.component.ts b/community/components/buttons/floating-button/floating-button.component.ts similarity index 59% rename from community/components/overlay/floating-button/floating-button.component.ts rename to community/components/buttons/floating-button/floating-button.component.ts index 222234fd2..57718f32f 100644 --- a/community/components/overlay/floating-button/floating-button.component.ts +++ b/community/components/buttons/floating-button/floating-button.component.ts @@ -18,6 +18,7 @@ export type FloatingButtonOffset = { selector: "[tedi-floating-button]", host: { "[class]": "classes()", + "[style]": "positioning()", }, }) export class FloatingButtonDirective { @@ -30,7 +31,7 @@ export class FloatingButtonDirective { * Button visual type * @default primary */ - visualType = input(); + visualType = input("primary"); /** * Button size * @default medium @@ -71,4 +72,38 @@ export class FloatingButtonDirective { } return classes.join(" "); }); + + positioning = computed(() => { + const styles: { [key: string]: string } = {}; + const placement = this.placement(); + const offset = this.offset(); + if (!placement || !offset) { + return ""; + } + if (placement.vertical === "top") { + styles["top"] = offset.top ? offset.top.toString() : "1.5rem"; + } + if (placement.vertical === "bottom") { + styles["bottom"] = offset.bottom ? offset.bottom.toString() : "1.5rem"; + } + if (placement.vertical === "center") { + styles["top"] = "50%"; + styles["transform"] = "translateY(-50%)"; + } + if (placement.horizontal === "left") { + styles["left"] = offset.left ? offset.left.toString() : "0"; + } + if (placement.horizontal === "right") { + styles["right"] = offset.right ? offset.right.toString() : "0"; + } + if (placement.horizontal === "center") { + styles["left"] = "50%"; + styles["transform"] = styles["transform"] + ? styles["transform"] + " translateX(-50%)" + : "translateX(-50%)"; + } + console.log(styles, JSON.stringify(styles)); + + return JSON.stringify(styles); + }); } diff --git a/community/components/overlay/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts similarity index 97% rename from community/components/overlay/floating-button/floating-button.stories.ts rename to community/components/buttons/floating-button/floating-button.stories.ts index e060ad342..e8e3d1a2f 100644 --- a/community/components/overlay/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -7,7 +7,7 @@ const buttonSizeArray = ["medium", "large"]; const buttonStateArray = ["Default", "Hover", "Active", "Focus"]; const meta: Meta = { - title: "Components/Overlay/Floating Button", + title: "Community/Buttons/Floating Button", component: FloatingButtonDirective, decorators: [ diff --git a/tedi/components/buttons/button/button.component.scss b/tedi/components/buttons/button/button.component.scss index 4efdeac2c..4f2d6c3c2 100644 --- a/tedi/components/buttons/button/button.component.scss +++ b/tedi/components/buttons/button/button.component.scss @@ -280,37 +280,44 @@ $btn-height-large: 3rem; @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); } - // &.tedi-floating-button--primary { - // @include button-content.button-variant( - // var(--button-floating-primary-background-default), - // var(--button-floating-primary-border-default), - // var(--button-floating-primary-text-default), - // var(--button-floating-primary-background-hover), - // var(--button-floating-primary-border-hover), - // var(--button-floating-primary-text-default), - // var(--button-floating-primary-border-focus), - // var(--button-floating-primary-background-focus), - // var(--button-floating-primary-text-default), - // var(--button-floating-primary-border-active), - // var(--button-floating-primary-background-active) - // ); - // } - - // &.tedi-floating-button--secondary { - // @include button-content.button-variant( - // var(--button-floating-secondary-background-default), - // var(--button-floating-secondary-border-default), - // var(--button-floating-secondary-text-default), - // var(--button-floating-secondary-background-hover), - // var(--button-floating-secondary-border-hover), - // var(--button-floating-secondary-text-hover), - // var(--button-floating-primary-border-focus), - // var(--button-floating-secondary-background-focus), - // var(--button-floating-secondary-text-active), - // var(--button-floating-secondary-border-active), - // var(--button-floating-secondary-background-active) - // ); - // } + &.tedi-floating-button--primary { + --_btn-bg: var(--button-main-primary-background-default); + + --_btn-border: var(--button-main-primary-border-default); + --_btn-text: var(--button-main-primary-text-default); + --_btn-outline: var(--primary-500); + // @include button-content.button-variant( + // var(--button-floating-primary-background-default), + // var(--button-floating-primary-border-default), + // var(--button-floating-primary-text-default), + // var(--button-floating-primary-background-hover), + // var(--button-floating-primary-border-hover), + // var(--button-floating-primary-text-default), + // var(--button-floating-primary-border-focus), + // var(--button-floating-primary-background-focus), + // var(--button-floating-primary-text-default), + // var(--button-floating-primary-border-active), + // var(--button-floating-primary-background-active) + // ); + } + + &.tedi-floating-button--secondary { + @include button-variant-color-vars("secondary"); + + // @include button-content.button-variant( + // var(--button-floating-secondary-background-default), + // var(--button-floating-secondary-border-default), + // var(--button-floating-secondary-text-default), + // var(--button-floating-secondary-background-hover), + // var(--button-floating-secondary-border-hover), + // var(--button-floating-secondary-text-hover), + // var(--button-floating-primary-border-focus), + // var(--button-floating-secondary-background-focus), + // var(--button-floating-secondary-text-active), + // var(--button-floating-secondary-border-active), + // var(--button-floating-secondary-background-active) + // ); + } &--medium { height: auto; From 60926d3cd3c30c0756c5d1386bdcfa4dad9eb912 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:24:36 +0200 Subject: [PATCH 04/23] feat(floating-button): use component instead of directive, move styles to floating button #195 --- .../floating-button.component.ts | 85 +++----------- .../floating-button.stories.ts | 43 ++++--- .../floating-button.styles.scss | 105 +++++++++++++++++ .../buttons/button/button.component.scss | 108 ------------------ 4 files changed, 148 insertions(+), 193 deletions(-) create mode 100644 community/components/buttons/floating-button/floating-button.styles.scss diff --git a/community/components/buttons/floating-button/floating-button.component.ts b/community/components/buttons/floating-button/floating-button.component.ts index 57718f32f..27837aca2 100644 --- a/community/components/buttons/floating-button/floating-button.component.ts +++ b/community/components/buttons/floating-button/floating-button.component.ts @@ -1,8 +1,9 @@ -import { computed, Directive, input } from "@angular/core"; +import { Component, computed, input, ViewEncapsulation } from "@angular/core"; +import { ButtonComponent, ButtonVariant } from "tedi/components"; export type FloatingButtonAxis = "horizontal" | "vertical"; export type FloatingButtonColor = "primary" | "secondary"; -export type FloatingButtonSize = "medium" | "large"; +export type FloatingButtonSize = "small" | "medium" | "large"; export type FloatingButtonPlacement = { vertical: "top" | "bottom" | "center"; horizontal: "left" | "right" | "center"; @@ -14,14 +15,21 @@ export type FloatingButtonOffset = { right?: number | string; }; -@Directive({ - selector: "[tedi-floating-button]", - host: { - "[class]": "classes()", - "[style]": "positioning()", - }, +@Component({ + selector: "tedi-floating-button", + template: ``, + imports: [ButtonComponent], + encapsulation: ViewEncapsulation.None, }) -export class FloatingButtonDirective { +export class FloatingButtonComponent { + id = input(); + /** + * Specifies the color theme of the button. The color should meet accessibility standards for color contrast. + * @default primary + */ + variant = input("primary"); /** * Button axis * @default horizontal @@ -37,27 +45,6 @@ export class FloatingButtonDirective { * @default medium */ size = input("medium"); - /** - * Button positionwu - * @default fixed - */ - position = input("fixed"); - /** - * Button placement - * @default { vertical: 'bottom', horizontal: 'right' } - */ - placement = input({ - vertical: "bottom", - horizontal: "right", - }); - /** - * Button offset - * @default { bottom: '1.5rem', right: '1.5rem' } - */ - offset = input({ - bottom: "1.5rem", - right: "1.5rem", - }); classes = computed(() => { const classes = ["tedi-floating-button"]; @@ -67,43 +54,7 @@ export class FloatingButtonDirective { if (this.visualType()) { classes.push(`tedi-floating-button--${this.visualType()}`); } - if (this.size) { - classes.push(`tedi-floating-button--${this.size()}`); - } + classes.push(`tedi-floating-button--${this.size() ?? "medium"}`); return classes.join(" "); }); - - positioning = computed(() => { - const styles: { [key: string]: string } = {}; - const placement = this.placement(); - const offset = this.offset(); - if (!placement || !offset) { - return ""; - } - if (placement.vertical === "top") { - styles["top"] = offset.top ? offset.top.toString() : "1.5rem"; - } - if (placement.vertical === "bottom") { - styles["bottom"] = offset.bottom ? offset.bottom.toString() : "1.5rem"; - } - if (placement.vertical === "center") { - styles["top"] = "50%"; - styles["transform"] = "translateY(-50%)"; - } - if (placement.horizontal === "left") { - styles["left"] = offset.left ? offset.left.toString() : "0"; - } - if (placement.horizontal === "right") { - styles["right"] = offset.right ? offset.right.toString() : "0"; - } - if (placement.horizontal === "center") { - styles["left"] = "50%"; - styles["transform"] = styles["transform"] - ? styles["transform"] + " translateX(-50%)" - : "translateX(-50%)"; - } - console.log(styles, JSON.stringify(styles)); - - return JSON.stringify(styles); - }); } diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index e8e3d1a2f..8eb84d5b9 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -1,25 +1,24 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; -import { FloatingButtonDirective } from "./floating-button.component"; -import { ButtonComponent } from "tedi/components"; +import { FloatingButtonComponent } from "./floating-button.component"; import { NgFor } from "@angular/common"; const buttonSizeArray = ["medium", "large"]; const buttonStateArray = ["Default", "Hover", "Active", "Focus"]; -const meta: Meta = { +const meta: Meta = { title: "Community/Buttons/Floating Button", - component: FloatingButtonDirective, + component: FloatingButtonComponent, decorators: [ moduleMetadata({ - imports: [FloatingButtonDirective, ButtonComponent, NgFor], + imports: [FloatingButtonComponent, NgFor], }), ], }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = { parameters: { @@ -30,7 +29,7 @@ export const Default: Story = { }, }, render: () => ({ - template: ``, + template: `floating button`, }), }; @@ -39,9 +38,11 @@ export const sizesVertical: Story = { render: (args) => ({ props: { ...args, buttonSizeArray }, template: ` - @for(size of buttonSizeArray; track size) { - - }`, +
+ @for(size of buttonSizeArray; track size) { + {{size}}Floating Button + } +
`, }), }; @@ -50,9 +51,11 @@ export const statesVertical: Story = { render: (args) => ({ props: { ...args, buttonStateArray }, template: ` - @for(state of buttonStateArray; track state) { - - }`, +
+ @for(state of buttonStateArray; track state) { + {{state}}Floating Button + } +
`, }), }; @@ -61,9 +64,11 @@ export const sizesHorizontal: Story = { render: (args) => ({ props: { ...args, buttonSizeArray }, template: ` - @for(size of buttonSizeArray; track size) { - - }`, +
+ @for(size of buttonSizeArray; track size) { + {{size}}Floating Button + } +
`, }), }; @@ -72,8 +77,10 @@ export const statesHorizontal: Story = { render: (args) => ({ props: { ...args, buttonStateArray }, template: ` +
@for(state of buttonStateArray; track state) { - - }`, + {{state}}Floating Button + } +
`, }), }; diff --git a/community/components/buttons/floating-button/floating-button.styles.scss b/community/components/buttons/floating-button/floating-button.styles.scss new file mode 100644 index 000000000..e12b45293 --- /dev/null +++ b/community/components/buttons/floating-button/floating-button.styles.scss @@ -0,0 +1,105 @@ +$btn-height: 2.75rem; +$btn-height-small: 2.5rem; +$btn-height-large: 3rem; + +.tedi-floating-button { + box-sizing: border-box; + text-wrap: wrap; + box-shadow: 0 4px 10px 0 var(--alpha-14); + + &--horizontal { + @include mixins.responsive-styles(border-radius, button-radius-default); + } + + &--vertical { + min-width: max-content; + white-space: nowrap; + border-radius: 0; + transform: rotate(-90deg); + transform-origin: center; + + @include mixins.responsive-styles( + border-top-right-radius, + button-radius-sm + ); + @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); + } + + &.tedi-floating-button--primary { + --_btn-bg: var(--button-main-primary-background-default); + + --_btn-border: var(--button-main-primary-border-default); + --_btn-text: var(--button-main-primary-text-default); + --_btn-outline: var(--primary-500); + // @include button-content.button-variant( + // var(--button-floating-primary-background-default), + // var(--button-floating-primary-border-default), + // var(--button-floating-primary-text-default), + // var(--button-floating-primary-background-hover), + // var(--button-floating-primary-border-hover), + // var(--button-floating-primary-text-default), + // var(--button-floating-primary-border-focus), + // var(--button-floating-primary-background-focus), + // var(--button-floating-primary-text-default), + // var(--button-floating-primary-border-active), + // var(--button-floating-primary-background-active) + // ); + } + + &.tedi-floating-button--secondary { + // @include button-content.button-variant( + // var(--button-floating-secondary-background-default), + // var(--button-floating-secondary-border-default), + // var(--button-floating-secondary-text-default), + // var(--button-floating-secondary-background-hover), + // var(--button-floating-secondary-border-hover), + // var(--button-floating-secondary-text-hover), + // var(--button-floating-primary-border-focus), + // var(--button-floating-secondary-background-focus), + // var(--button-floating-secondary-text-active), + // var(--button-floating-secondary-border-active), + // var(--button-floating-secondary-background-active) + // ); + } + + &--medium { + height: auto; + min-height: $btn-height; + + @include breakpoints.media-breakpoint-up(md) { + min-height: $btn-height-small; + } + + @include mixins.responsive-styles( + padding, + button-md-padding-y button-md-padding-x + ); + + &.tedi-floating-button--icon-only { + @include mixins.responsive-styles(width, button-md-icon-size); + @include mixins.responsive-styles(height, button-md-icon-size); + @include mixins.responsive-styles(padding, button-md-icon-padding); + } + } + + &--large { + height: auto; + min-height: $btn-height-large; + + @include mixins.responsive-styles( + padding, + button-xl-padding-y button-xl-padding-x + ); + + &.tedi-floating-button--icon-only { + @include mixins.responsive-styles(width, button-xl-icon-size); + @include mixins.responsive-styles(height, button-xl-icon-size); + @include mixins.responsive-styles(padding, button-xl-icon-padding); + } + } + + &:not(&--icon-only) { + @include mixins.responsive-styles(min-width, button-width-min); + @include mixins.responsive-styles(max-width, button-width-max); + } +} diff --git a/tedi/components/buttons/button/button.component.scss b/tedi/components/buttons/button/button.component.scss index 4f2d6c3c2..aa9ea2f76 100644 --- a/tedi/components/buttons/button/button.component.scss +++ b/tedi/components/buttons/button/button.component.scss @@ -252,111 +252,3 @@ $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; font-size: inherit; } } - -$btn-height: 2.75rem; -$btn-height-small: 2.5rem; -$btn-height-large: 3rem; - -.tedi-floating-button { - box-sizing: border-box; - text-wrap: wrap; - box-shadow: 0 4px 10px 0 var(--alpha-14); - - &--horizontal { - @include mixins.responsive-styles(border-radius, button-radius-default); - } - - &--vertical { - min-width: max-content; - white-space: nowrap; - border-radius: 0; - transform: rotate(-90deg); - transform-origin: center; - - @include mixins.responsive-styles( - border-top-right-radius, - button-radius-sm - ); - @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); - } - - &.tedi-floating-button--primary { - --_btn-bg: var(--button-main-primary-background-default); - - --_btn-border: var(--button-main-primary-border-default); - --_btn-text: var(--button-main-primary-text-default); - --_btn-outline: var(--primary-500); - // @include button-content.button-variant( - // var(--button-floating-primary-background-default), - // var(--button-floating-primary-border-default), - // var(--button-floating-primary-text-default), - // var(--button-floating-primary-background-hover), - // var(--button-floating-primary-border-hover), - // var(--button-floating-primary-text-default), - // var(--button-floating-primary-border-focus), - // var(--button-floating-primary-background-focus), - // var(--button-floating-primary-text-default), - // var(--button-floating-primary-border-active), - // var(--button-floating-primary-background-active) - // ); - } - - &.tedi-floating-button--secondary { - @include button-variant-color-vars("secondary"); - - // @include button-content.button-variant( - // var(--button-floating-secondary-background-default), - // var(--button-floating-secondary-border-default), - // var(--button-floating-secondary-text-default), - // var(--button-floating-secondary-background-hover), - // var(--button-floating-secondary-border-hover), - // var(--button-floating-secondary-text-hover), - // var(--button-floating-primary-border-focus), - // var(--button-floating-secondary-background-focus), - // var(--button-floating-secondary-text-active), - // var(--button-floating-secondary-border-active), - // var(--button-floating-secondary-background-active) - // ); - } - - &--medium { - height: auto; - min-height: $btn-height; - - @include breakpoints.media-breakpoint-up(md) { - min-height: $btn-height-small; - } - - @include mixins.responsive-styles( - padding, - button-md-padding-y button-md-padding-x - ); - - &.tedi-floating-button--icon-only { - @include mixins.responsive-styles(width, button-md-icon-size); - @include mixins.responsive-styles(height, button-md-icon-size); - @include mixins.responsive-styles(padding, button-md-icon-padding); - } - } - - &--large { - height: auto; - min-height: $btn-height-large; - - @include mixins.responsive-styles( - padding, - button-xl-padding-y button-xl-padding-x - ); - - &.tedi-floating-button--icon-only { - @include mixins.responsive-styles(width, button-xl-icon-size); - @include mixins.responsive-styles(height, button-xl-icon-size); - @include mixins.responsive-styles(padding, button-xl-icon-padding); - } - } - - &:not(&--icon-only) { - @include mixins.responsive-styles(min-width, button-width-min); - @include mixins.responsive-styles(max-width, button-width-max); - } -} From 7b0b1caa26a44c31bb379847104b2386cddf84c2 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Thu, 13 Nov 2025 17:04:30 +0200 Subject: [PATCH 05/23] feat(floating-button): more style/storybook changes #195 --- .../floating-button.component.scss | 141 ++++++++++++++++++ .../floating-button.component.ts | 25 +--- .../floating-button.stories.ts | 40 +++-- .../floating-button.styles.scss | 105 ------------- 4 files changed, 175 insertions(+), 136 deletions(-) create mode 100644 community/components/buttons/floating-button/floating-button.component.scss delete mode 100644 community/components/buttons/floating-button/floating-button.styles.scss diff --git a/community/components/buttons/floating-button/floating-button.component.scss b/community/components/buttons/floating-button/floating-button.component.scss new file mode 100644 index 000000000..a38aa6dc6 --- /dev/null +++ b/community/components/buttons/floating-button/floating-button.component.scss @@ -0,0 +1,141 @@ +@use "@tedi-design-system/core/mixins"; +@use "@tedi-design-system/core/bootstrap-utility/breakpoints"; + +$btn-height: 2.75rem; +$btn-height-small: 2.5rem; +$btn-height-large: 3rem; + +@mixin button-variant( + $background, + $border, + $color, + $background-hover, + $border-hover, + $color-hover, + $border-focus, + $background-focus, + $color-active, + $border-active, + $background-active, + $background-disabled: var(--button-main-disabled-general-background), + $border-disabled: var(--button-main-disabled-general-border), + $color-disabled: var(--button-main-disabled-general-text) +) { + color: $color; + background-color: $background; + border-color: $border; + + --_btn-hover-bg: $background-hover; + --_btn-hover-border: $border-hover; + --_btn-hover-text: $color-hover; + + --_btn-active-bg: $background-active; + --_btn-active-border: $border-active; + --_btn-active-text: $color-active; + + --_btn-disabled-bg: $background-disabled; + --_btn-disabled-border: $border-disabled; + --_btn-disabled-text: $color-disabled; + + --_btn-focus-bg: $background-focus; + --_btn-focus-border: $border; + --_btn-focus-text: $color; + --_btn-focus-outline: $border-focus; +} + +button.tedi-floating-button { + box-sizing: border-box; + text-wrap: wrap; + box-shadow: 0 4px 10px 0 var(--alpha-14); + + &--horizontal { + @include mixins.responsive-styles(border-radius, button-radius-default); + } + + &--vertical { + min-width: max-content; + white-space: nowrap; + border-radius: 0; + transform: rotate(-90deg); + transform-origin: center; + + @include mixins.responsive-styles( + border-top-right-radius, + button-radius-sm + ); + @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); + } +tedi-floating-button--vertical + &.tedi-floating-button--primary { + @include button-variant( + var(--button-floating-primary-background-default), + var(--button-floating-primary-border-default), + var(--button-floating-primary-text-default), + var(--button-floating-primary-background-hover), + var(--button-floating-primary-border-hover), + var(--button-floating-primary-text-default), + var(--button-floating-primary-border-focus), + var(--button-floating-primary-background-focus), + var(--button-floating-primary-text-default), + var(--button-floating-primary-border-active), + var(--button-floating-primary-background-active) + ); + } + + &.tedi-floating-button--secondary { + @include button-variant( + var(--button-floating-secondary-background-default), + var(--button-floating-secondary-border-default), + var(--button-floating-secondary-text-default), + var(--button-floating-secondary-background-hover), + var(--button-floating-secondary-border-hover), + var(--button-floating-secondary-text-hover), + var(--button-floating-primary-border-focus), + var(--button-floating-secondary-background-focus), + var(--button-floating-secondary-text-active), + var(--button-floating-secondary-border-active), + var(--button-floating-secondary-background-active) + ); + } + + &--medium { + height: auto; + min-height: $btn-height; + + @include breakpoints.media-breakpoint-up(md) { + min-height: $btn-height-small; + } + + @include mixins.responsive-styles( + padding, + button-md-padding-y button-md-padding-x + ); + + &.tedi-floating-button--icon-only { + @include mixins.responsive-styles(width, button-md-icon-size); + @include mixins.responsive-styles(height, button-md-icon-size); + @include mixins.responsive-styles(padding, button-md-icon-padding); + } + } + + &--large { + height: auto; + min-height: $btn-height-large; + + @include mixins.responsive-styles( + padding, + button-xl-padding-y button-xl-padding-x + ); + + &.tedi-floating-button--icon-only { + @include mixins.responsive-styles(width, button-xl-icon-size); + @include mixins.responsive-styles(height, button-xl-icon-size); + @include mixins.responsive-styles(padding, button-xl-icon-padding); + } + } + + &:not(&--icon-only) { + @include mixins.responsive-styles(min-width, button-width-min); + @include mixins.responsive-styles(max-width, button-width-max); + } +} diff --git a/community/components/buttons/floating-button/floating-button.component.ts b/community/components/buttons/floating-button/floating-button.component.ts index 27837aca2..ad14b48c5 100644 --- a/community/components/buttons/floating-button/floating-button.component.ts +++ b/community/components/buttons/floating-button/floating-button.component.ts @@ -2,24 +2,14 @@ import { Component, computed, input, ViewEncapsulation } from "@angular/core"; import { ButtonComponent, ButtonVariant } from "tedi/components"; export type FloatingButtonAxis = "horizontal" | "vertical"; -export type FloatingButtonColor = "primary" | "secondary"; export type FloatingButtonSize = "small" | "medium" | "large"; -export type FloatingButtonPlacement = { - vertical: "top" | "bottom" | "center"; - horizontal: "left" | "right" | "center"; -}; -export type FloatingButtonOffset = { - top?: number | string; - bottom?: number | string; - left?: number | string; - right?: number | string; -}; @Component({ selector: "tedi-floating-button", template: ``, + styleUrl: "./floating-button.component.scss", imports: [ButtonComponent], encapsulation: ViewEncapsulation.None, }) @@ -35,11 +25,6 @@ export class FloatingButtonComponent { * @default horizontal */ axis = input("horizontal"); - /** - * Button visual type - * @default primary - */ - visualType = input("primary"); /** * Button size * @default medium @@ -48,13 +33,9 @@ export class FloatingButtonComponent { classes = computed(() => { const classes = ["tedi-floating-button"]; - if (this.axis()) { - classes.push(`tedi-floating-button--${this.axis()}`); - } - if (this.visualType()) { - classes.push(`tedi-floating-button--${this.visualType()}`); - } + classes.push(`tedi-floating-button--${this.axis() ?? "horizontal"}`); classes.push(`tedi-floating-button--${this.size() ?? "medium"}`); + classes.push(`tedi-floating-button--${this.variant() ?? "primary"}`); return classes.join(" "); }); } diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index 8eb84d5b9..71da2065f 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -1,6 +1,6 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { FloatingButtonComponent } from "./floating-button.component"; -import { NgFor } from "@angular/common"; +import { IconComponent } from "tedi/components"; const buttonSizeArray = ["medium", "large"]; const buttonStateArray = ["Default", "Hover", "Active", "Focus"]; @@ -11,7 +11,7 @@ const meta: Meta = { decorators: [ moduleMetadata({ - imports: [FloatingButtonComponent, NgFor], + imports: [FloatingButtonComponent, IconComponent], }), ], }; @@ -38,9 +38,18 @@ export const sizesVertical: Story = { render: (args) => ({ props: { ...args, buttonSizeArray }, template: ` -
+
@for(size of buttonSizeArray; track size) { - {{size}}Floating Button +
+

{{size}}

+ Floating Button + Floating Button + + + Floating Button + Floating Button + +
}
`, }), @@ -51,9 +60,16 @@ export const statesVertical: Story = { render: (args) => ({ props: { ...args, buttonStateArray }, template: ` -
+
@for(state of buttonStateArray; track state) { - {{state}}Floating Button +
+

{{state}}

+ Floating Button + + + Floating Button + Floating Button +
}
`, }), @@ -64,9 +80,15 @@ export const sizesHorizontal: Story = { render: (args) => ({ props: { ...args, buttonSizeArray }, template: ` -
+
@for(size of buttonSizeArray; track size) { - {{size}}Floating Button +
+ {{size}} + Floating Button + Floating Button + Floating Button + +
}
`, }), @@ -77,7 +99,7 @@ export const statesHorizontal: Story = { render: (args) => ({ props: { ...args, buttonStateArray }, template: ` -
+
@for(state of buttonStateArray; track state) { {{state}}Floating Button } diff --git a/community/components/buttons/floating-button/floating-button.styles.scss b/community/components/buttons/floating-button/floating-button.styles.scss deleted file mode 100644 index e12b45293..000000000 --- a/community/components/buttons/floating-button/floating-button.styles.scss +++ /dev/null @@ -1,105 +0,0 @@ -$btn-height: 2.75rem; -$btn-height-small: 2.5rem; -$btn-height-large: 3rem; - -.tedi-floating-button { - box-sizing: border-box; - text-wrap: wrap; - box-shadow: 0 4px 10px 0 var(--alpha-14); - - &--horizontal { - @include mixins.responsive-styles(border-radius, button-radius-default); - } - - &--vertical { - min-width: max-content; - white-space: nowrap; - border-radius: 0; - transform: rotate(-90deg); - transform-origin: center; - - @include mixins.responsive-styles( - border-top-right-radius, - button-radius-sm - ); - @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); - } - - &.tedi-floating-button--primary { - --_btn-bg: var(--button-main-primary-background-default); - - --_btn-border: var(--button-main-primary-border-default); - --_btn-text: var(--button-main-primary-text-default); - --_btn-outline: var(--primary-500); - // @include button-content.button-variant( - // var(--button-floating-primary-background-default), - // var(--button-floating-primary-border-default), - // var(--button-floating-primary-text-default), - // var(--button-floating-primary-background-hover), - // var(--button-floating-primary-border-hover), - // var(--button-floating-primary-text-default), - // var(--button-floating-primary-border-focus), - // var(--button-floating-primary-background-focus), - // var(--button-floating-primary-text-default), - // var(--button-floating-primary-border-active), - // var(--button-floating-primary-background-active) - // ); - } - - &.tedi-floating-button--secondary { - // @include button-content.button-variant( - // var(--button-floating-secondary-background-default), - // var(--button-floating-secondary-border-default), - // var(--button-floating-secondary-text-default), - // var(--button-floating-secondary-background-hover), - // var(--button-floating-secondary-border-hover), - // var(--button-floating-secondary-text-hover), - // var(--button-floating-primary-border-focus), - // var(--button-floating-secondary-background-focus), - // var(--button-floating-secondary-text-active), - // var(--button-floating-secondary-border-active), - // var(--button-floating-secondary-background-active) - // ); - } - - &--medium { - height: auto; - min-height: $btn-height; - - @include breakpoints.media-breakpoint-up(md) { - min-height: $btn-height-small; - } - - @include mixins.responsive-styles( - padding, - button-md-padding-y button-md-padding-x - ); - - &.tedi-floating-button--icon-only { - @include mixins.responsive-styles(width, button-md-icon-size); - @include mixins.responsive-styles(height, button-md-icon-size); - @include mixins.responsive-styles(padding, button-md-icon-padding); - } - } - - &--large { - height: auto; - min-height: $btn-height-large; - - @include mixins.responsive-styles( - padding, - button-xl-padding-y button-xl-padding-x - ); - - &.tedi-floating-button--icon-only { - @include mixins.responsive-styles(width, button-xl-icon-size); - @include mixins.responsive-styles(height, button-xl-icon-size); - @include mixins.responsive-styles(padding, button-xl-icon-padding); - } - } - - &:not(&--icon-only) { - @include mixins.responsive-styles(min-width, button-width-min); - @include mixins.responsive-styles(max-width, button-width-max); - } -} From c5c8dea378900304ea5daa003d686a71e8b7f932 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Thu, 13 Nov 2025 17:54:49 +0200 Subject: [PATCH 06/23] feat(floating-button): storybook polishing #195 --- .../floating-button.component.scss | 11 ++- .../floating-button.stories.ts | 72 ++++++++----------- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/community/components/buttons/floating-button/floating-button.component.scss b/community/components/buttons/floating-button/floating-button.component.scss index a38aa6dc6..ecdfb1742 100644 --- a/community/components/buttons/floating-button/floating-button.component.scss +++ b/community/components/buttons/floating-button/floating-button.component.scss @@ -43,6 +43,14 @@ $btn-height-large: 3rem; --_btn-focus-outline: $border-focus; } +:host { + display: contents; +} + +.test { + display: contents; +} + button.tedi-floating-button { box-sizing: border-box; text-wrap: wrap; @@ -65,8 +73,7 @@ button.tedi-floating-button { ); @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); } -tedi-floating-button--vertical - &.tedi-floating-button--primary { + tedi-floating-button--vertical &.tedi-floating-button--primary { @include button-variant( var(--button-floating-primary-background-default), var(--button-floating-primary-border-default), diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index 71da2065f..02b61d53b 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -1,6 +1,6 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { FloatingButtonComponent } from "./floating-button.component"; -import { IconComponent } from "tedi/components"; +import { IconComponent, TextComponent } from "tedi/components"; const buttonSizeArray = ["medium", "large"]; const buttonStateArray = ["Default", "Hover", "Active", "Focus"]; @@ -11,7 +11,7 @@ const meta: Meta = { decorators: [ moduleMetadata({ - imports: [FloatingButtonComponent, IconComponent], + imports: [FloatingButtonComponent, IconComponent, TextComponent], }), ], }; @@ -35,20 +35,23 @@ export const Default: Story = { export const sizesVertical: Story = { ...Default, + args: { + axis: "vertical", + }, render: (args) => ({ props: { ...args, buttonSizeArray }, template: ` -
+
@for(size of buttonSizeArray; track size) {
-

{{size}}

- Floating Button - Floating Button - +
{{size}}
+ Floating Button + Floating Button + - Floating Button - Floating Button - + Floating Button + Floating Button +
}
`, @@ -57,18 +60,21 @@ export const sizesVertical: Story = { export const statesVertical: Story = { ...Default, + args: { + axis: "vertical", + }, render: (args) => ({ props: { ...args, buttonStateArray }, template: ` -
+
@for(state of buttonStateArray; track state) {
-

{{state}}

- Floating Button - +
{{state}}
+ Floating Button + - Floating Button - Floating Button + Floating Button + Floating Button
}
`, @@ -76,33 +82,15 @@ export const statesVertical: Story = { }; export const sizesHorizontal: Story = { - ...Default, - render: (args) => ({ - props: { ...args, buttonSizeArray }, - template: ` -
- @for(size of buttonSizeArray; track size) { -
- {{size}} - Floating Button - Floating Button - Floating Button - -
- } -
`, - }), + ...sizesVertical, + args: { + axis: "horizontal", + }, }; export const statesHorizontal: Story = { - ...Default, - render: (args) => ({ - props: { ...args, buttonStateArray }, - template: ` -
- @for(state of buttonStateArray; track state) { - {{state}}Floating Button - } -
`, - }), + ...statesVertical, + args: { + axis: "horizontal", + }, }; From 439f45576e5c12e4b5bbc16bf41f8804524b927d Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:46:34 +0200 Subject: [PATCH 07/23] feat(floating-button): fix icon only #195 --- .../floating-button/floating-button.component.scss | 7 +++---- .../floating-button/floating-button.stories.ts | 14 ++++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/community/components/buttons/floating-button/floating-button.component.scss b/community/components/buttons/floating-button/floating-button.component.scss index ecdfb1742..85430ebaf 100644 --- a/community/components/buttons/floating-button/floating-button.component.scss +++ b/community/components/buttons/floating-button/floating-button.component.scss @@ -107,7 +107,6 @@ button.tedi-floating-button { &--medium { height: auto; - min-height: $btn-height; @include breakpoints.media-breakpoint-up(md) { min-height: $btn-height-small; @@ -118,7 +117,7 @@ button.tedi-floating-button { button-md-padding-y button-md-padding-x ); - &.tedi-floating-button--icon-only { + &.tedi-button--icon-only { @include mixins.responsive-styles(width, button-md-icon-size); @include mixins.responsive-styles(height, button-md-icon-size); @include mixins.responsive-styles(padding, button-md-icon-padding); @@ -134,14 +133,14 @@ button.tedi-floating-button { button-xl-padding-y button-xl-padding-x ); - &.tedi-floating-button--icon-only { + &.tedi-button--icon-only { @include mixins.responsive-styles(width, button-xl-icon-size); @include mixins.responsive-styles(height, button-xl-icon-size); @include mixins.responsive-styles(padding, button-xl-icon-padding); } } - &:not(&--icon-only) { + &:not(.tedi-button--icon-only) { @include mixins.responsive-styles(min-width, button-width-min); @include mixins.responsive-styles(max-width, button-width-max); } diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index 02b61d53b..385f505ae 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -1,8 +1,8 @@ import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { FloatingButtonComponent } from "./floating-button.component"; -import { IconComponent, TextComponent } from "tedi/components"; +import { IconComponent } from "tedi/components"; -const buttonSizeArray = ["medium", "large"]; +const buttonSizeArray = ["small", "large"]; const buttonStateArray = ["Default", "Hover", "Active", "Focus"]; const meta: Meta = { @@ -11,7 +11,7 @@ const meta: Meta = { decorators: [ moduleMetadata({ - imports: [FloatingButtonComponent, IconComponent, TextComponent], + imports: [FloatingButtonComponent, IconComponent], }), ], }; @@ -51,7 +51,7 @@ export const sizesVertical: Story = { Floating Button Floating Button - +
}
`, @@ -71,10 +71,12 @@ export const statesVertical: Story = {
{{state}}
Floating Button + Floating Button - Floating Button - Floating Button + Floating Button + Floating Button +
}
`, From 4f8001133e96421b42fd24537de36b9def7824b6 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:47:00 +0200 Subject: [PATCH 08/23] feat(floating-button): keep min-height #195 --- .../buttons/floating-button/floating-button.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/community/components/buttons/floating-button/floating-button.component.scss b/community/components/buttons/floating-button/floating-button.component.scss index 85430ebaf..dc112c31c 100644 --- a/community/components/buttons/floating-button/floating-button.component.scss +++ b/community/components/buttons/floating-button/floating-button.component.scss @@ -107,6 +107,7 @@ button.tedi-floating-button { &--medium { height: auto; + min-height: $btn-height; @include breakpoints.media-breakpoint-up(md) { min-height: $btn-height-small; From 4537ea8f3848a9bffb0b0c487efc8d6763fbbdf7 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:21:10 +0200 Subject: [PATCH 09/23] feat(floating-button): finishing touches on story to fix text aligment + args #195 --- .../floating-button.stories.ts | 64 +++++++++++++++++-- .../buttons/button/button.component.scss | 1 - 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index 385f505ae..31f0e3709 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -1,11 +1,22 @@ -import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { + argsToTemplate, + Meta, + moduleMetadata, + StoryObj, +} from "@storybook/angular"; import { FloatingButtonComponent } from "./floating-button.component"; import { IconComponent } from "tedi/components"; const buttonSizeArray = ["small", "large"]; const buttonStateArray = ["Default", "Hover", "Active", "Focus"]; -const meta: Meta = { +interface StoryArgs { + textOffset: string; +} + +type StoryFloatingButtonArgs = FloatingButtonComponent & StoryArgs; + +const meta: Meta = { title: "Community/Buttons/Floating Button", component: FloatingButtonComponent, @@ -14,11 +25,42 @@ const meta: Meta = { imports: [FloatingButtonComponent, IconComponent], }), ], + args: { + id: "", + variant: "primary", + axis: "horizontal", + size: "medium", + textOffset: "30px", + }, + argTypes: { + id: { control: "text" }, + variant: { + control: "select", + description: "Specifies the color theme of the button.", + options: ["primary", "secondary"], + }, + axis: { + control: "radio", + description: "Button axis, changes the orientation of the button.", + options: ["horizontal", "vertical"], + }, + size: { + control: "radio", + description: "Button size.", + options: ["small", "medium", "large"], + }, + // not meant to be user-editable or seen + textOffset: { + table: { + disable: true, + }, + }, + }, }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = { parameters: { @@ -27,9 +69,15 @@ export const Default: Story = { active: "#Active", focusVisible: "#Focus", }, + offset: -30, }, - render: () => ({ - template: `floating button`, + render: ({ textOffset: _textOffset, ...args }) => ({ + props: { ...args, debug: () => console.log("floating button clicked!") }, + template: ` +
+ floating button +
+ `, }), }; @@ -44,7 +92,7 @@ export const sizesVertical: Story = {
@for(size of buttonSizeArray; track size) {
-
{{size}}
+
{{size}}
Floating Button Floating Button @@ -69,7 +117,7 @@ export const statesVertical: Story = {
@for(state of buttonStateArray; track state) {
-
{{state}}
+
{{state}}
Floating Button Floating Button @@ -87,6 +135,7 @@ export const sizesHorizontal: Story = { ...sizesVertical, args: { axis: "horizontal", + textOffset: "0px", }, }; @@ -94,5 +143,6 @@ export const statesHorizontal: Story = { ...statesVertical, args: { axis: "horizontal", + textOffset: "0px", }, }; diff --git a/tedi/components/buttons/button/button.component.scss b/tedi/components/buttons/button/button.component.scss index aa9ea2f76..9bc60658e 100644 --- a/tedi/components/buttons/button/button.component.scss +++ b/tedi/components/buttons/button/button.component.scss @@ -1,5 +1,4 @@ @use "@tedi-design-system/core/mixins"; -@use "@tedi-design-system/core/bootstrap-utility/breakpoints"; @use "sass:list"; $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; From e7ec55c8c6a08373b2e61d9acf981356ca73d304 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:40:58 +0200 Subject: [PATCH 10/23] feat(floating-button): remove debug css #195 --- .../floating-button/floating-button.component.scss | 8 -------- 1 file changed, 8 deletions(-) diff --git a/community/components/buttons/floating-button/floating-button.component.scss b/community/components/buttons/floating-button/floating-button.component.scss index dc112c31c..e0cf41eef 100644 --- a/community/components/buttons/floating-button/floating-button.component.scss +++ b/community/components/buttons/floating-button/floating-button.component.scss @@ -43,14 +43,6 @@ $btn-height-large: 3rem; --_btn-focus-outline: $border-focus; } -:host { - display: contents; -} - -.test { - display: contents; -} - button.tedi-floating-button { box-sizing: border-box; text-wrap: wrap; From f962835132f33b4d4630a1c268e92280f770abbe Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:58:18 +0200 Subject: [PATCH 11/23] feat(floating-button): remove id #195 --- .../buttons/floating-button/floating-button.component.ts | 7 +++---- .../buttons/floating-button/floating-button.stories.ts | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/community/components/buttons/floating-button/floating-button.component.ts b/community/components/buttons/floating-button/floating-button.component.ts index ad14b48c5..38a35a238 100644 --- a/community/components/buttons/floating-button/floating-button.component.ts +++ b/community/components/buttons/floating-button/floating-button.component.ts @@ -2,7 +2,7 @@ import { Component, computed, input, ViewEncapsulation } from "@angular/core"; import { ButtonComponent, ButtonVariant } from "tedi/components"; export type FloatingButtonAxis = "horizontal" | "vertical"; -export type FloatingButtonSize = "small" | "medium" | "large"; +export type FloatingButtonSize = "default" | "small" | "medium" | "large"; @Component({ selector: "tedi-floating-button", @@ -14,7 +14,6 @@ export type FloatingButtonSize = "small" | "medium" | "large"; encapsulation: ViewEncapsulation.None, }) export class FloatingButtonComponent { - id = input(); /** * Specifies the color theme of the button. The color should meet accessibility standards for color contrast. * @default primary @@ -26,10 +25,10 @@ export class FloatingButtonComponent { */ axis = input("horizontal"); /** - * Button size + * Defines the size of the button * @default medium */ - size = input("medium"); + size = input("default"); classes = computed(() => { const classes = ["tedi-floating-button"]; diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index 31f0e3709..fa68af198 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -26,14 +26,12 @@ const meta: Meta = { }), ], args: { - id: "", variant: "primary", axis: "horizontal", size: "medium", textOffset: "30px", }, argTypes: { - id: { control: "text" }, variant: { control: "select", description: "Specifies the color theme of the button.", From 266e82ab2adaee575a4c36fd30f9003120949131 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Fri, 14 Nov 2025 18:21:07 +0200 Subject: [PATCH 12/23] feat(floating-button): refactor into selector component + trim styles #195 --- .../floating-button.component.scss | 121 +----------------- .../floating-button.component.ts | 31 ++--- .../floating-button.stories.ts | 31 +++-- .../buttons/button/button.component.scss | 46 ++++--- .../buttons/button/button.component.ts | 4 +- 5 files changed, 63 insertions(+), 170 deletions(-) diff --git a/community/components/buttons/floating-button/floating-button.component.scss b/community/components/buttons/floating-button/floating-button.component.scss index e0cf41eef..adb5195ec 100644 --- a/community/components/buttons/floating-button/floating-button.component.scss +++ b/community/components/buttons/floating-button/floating-button.component.scss @@ -1,53 +1,10 @@ @use "@tedi-design-system/core/mixins"; @use "@tedi-design-system/core/bootstrap-utility/breakpoints"; - -$btn-height: 2.75rem; -$btn-height-small: 2.5rem; -$btn-height-large: 3rem; - -@mixin button-variant( - $background, - $border, - $color, - $background-hover, - $border-hover, - $color-hover, - $border-focus, - $background-focus, - $color-active, - $border-active, - $background-active, - $background-disabled: var(--button-main-disabled-general-background), - $border-disabled: var(--button-main-disabled-general-border), - $color-disabled: var(--button-main-disabled-general-text) -) { - color: $color; - background-color: $background; - border-color: $border; - - --_btn-hover-bg: $background-hover; - --_btn-hover-border: $border-hover; - --_btn-hover-text: $color-hover; - - --_btn-active-bg: $background-active; - --_btn-active-border: $border-active; - --_btn-active-text: $color-active; - - --_btn-disabled-bg: $background-disabled; - --_btn-disabled-border: $border-disabled; - --_btn-disabled-text: $color-disabled; - - --_btn-focus-bg: $background-focus; - --_btn-focus-border: $border; - --_btn-focus-text: $color; - --_btn-focus-outline: $border-focus; -} +@forward "../../../../tedi/components/buttons/button/button.component.scss"; +@use "../../../../tedi/components/buttons/button/button.component.scss" as + buttonMixins; button.tedi-floating-button { - box-sizing: border-box; - text-wrap: wrap; - box-shadow: 0 4px 10px 0 var(--alpha-14); - &--horizontal { @include mixins.responsive-styles(border-radius, button-radius-default); } @@ -65,76 +22,12 @@ button.tedi-floating-button { ); @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); } - tedi-floating-button--vertical &.tedi-floating-button--primary { - @include button-variant( - var(--button-floating-primary-background-default), - var(--button-floating-primary-border-default), - var(--button-floating-primary-text-default), - var(--button-floating-primary-background-hover), - var(--button-floating-primary-border-hover), - var(--button-floating-primary-text-default), - var(--button-floating-primary-border-focus), - var(--button-floating-primary-background-focus), - var(--button-floating-primary-text-default), - var(--button-floating-primary-border-active), - var(--button-floating-primary-background-active) - ); - } &.tedi-floating-button--secondary { - @include button-variant( - var(--button-floating-secondary-background-default), - var(--button-floating-secondary-border-default), - var(--button-floating-secondary-text-default), - var(--button-floating-secondary-background-hover), - var(--button-floating-secondary-border-hover), - var(--button-floating-secondary-text-hover), - var(--button-floating-primary-border-focus), - var(--button-floating-secondary-background-focus), - var(--button-floating-secondary-text-active), - var(--button-floating-secondary-border-active), - var(--button-floating-secondary-background-active) + @include buttonMixins.button-variant-color-vars( + "secondary", + false, + "floating" ); } - - &--medium { - height: auto; - min-height: $btn-height; - - @include breakpoints.media-breakpoint-up(md) { - min-height: $btn-height-small; - } - - @include mixins.responsive-styles( - padding, - button-md-padding-y button-md-padding-x - ); - - &.tedi-button--icon-only { - @include mixins.responsive-styles(width, button-md-icon-size); - @include mixins.responsive-styles(height, button-md-icon-size); - @include mixins.responsive-styles(padding, button-md-icon-padding); - } - } - - &--large { - height: auto; - min-height: $btn-height-large; - - @include mixins.responsive-styles( - padding, - button-xl-padding-y button-xl-padding-x - ); - - &.tedi-button--icon-only { - @include mixins.responsive-styles(width, button-xl-icon-size); - @include mixins.responsive-styles(height, button-xl-icon-size); - @include mixins.responsive-styles(padding, button-xl-icon-padding); - } - } - - &:not(.tedi-button--icon-only) { - @include mixins.responsive-styles(min-width, button-width-min); - @include mixins.responsive-styles(max-width, button-width-max); - } } diff --git a/community/components/buttons/floating-button/floating-button.component.ts b/community/components/buttons/floating-button/floating-button.component.ts index 38a35a238..55846a39c 100644 --- a/community/components/buttons/floating-button/floating-button.component.ts +++ b/community/components/buttons/floating-button/floating-button.component.ts @@ -1,40 +1,29 @@ import { Component, computed, input, ViewEncapsulation } from "@angular/core"; -import { ButtonComponent, ButtonVariant } from "tedi/components"; +import { ButtonComponent } from "tedi/components"; export type FloatingButtonAxis = "horizontal" | "vertical"; -export type FloatingButtonSize = "default" | "small" | "medium" | "large"; @Component({ - selector: "tedi-floating-button", - template: ``, + selector: "[tedi-floating-button]", + template: ``, styleUrl: "./floating-button.component.scss", - imports: [ButtonComponent], encapsulation: ViewEncapsulation.None, + host: { + "[class]": "floatClasses()", + }, }) -export class FloatingButtonComponent { - /** - * Specifies the color theme of the button. The color should meet accessibility standards for color contrast. - * @default primary - */ - variant = input("primary"); +export class FloatingButtonComponent extends ButtonComponent { /** * Button axis * @default horizontal */ axis = input("horizontal"); - /** - * Defines the size of the button - * @default medium - */ - size = input("default"); - classes = computed(() => { + floatClasses = computed(() => { const classes = ["tedi-floating-button"]; classes.push(`tedi-floating-button--${this.axis() ?? "horizontal"}`); - classes.push(`tedi-floating-button--${this.size() ?? "medium"}`); classes.push(`tedi-floating-button--${this.variant() ?? "primary"}`); - return classes.join(" "); + + return `${classes.join(" ")} ${this.classes()}`; }); } diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index fa68af198..1514c7d33 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -7,7 +7,7 @@ import { import { FloatingButtonComponent } from "./floating-button.component"; import { IconComponent } from "tedi/components"; -const buttonSizeArray = ["small", "large"]; +const buttonSizeArray = ["default", "large"]; const buttonStateArray = ["Default", "Hover", "Active", "Focus"]; interface StoryArgs { @@ -28,7 +28,6 @@ const meta: Meta = { args: { variant: "primary", axis: "horizontal", - size: "medium", textOffset: "30px", }, argTypes: { @@ -45,7 +44,7 @@ const meta: Meta = { size: { control: "radio", description: "Button size.", - options: ["small", "medium", "large"], + options: ["small", "default", "large"], }, // not meant to be user-editable or seen textOffset: { @@ -73,7 +72,7 @@ export const Default: Story = { props: { ...args, debug: () => console.log("floating button clicked!") }, template: `
- floating button +
`, }), @@ -91,13 +90,13 @@ export const sizesVertical: Story = { @for(size of buttonSizeArray; track size) {
{{size}}
- Floating Button - Floating Button - + + + - Floating Button - Floating Button - + + +
}
`, @@ -116,13 +115,13 @@ export const statesVertical: Story = { @for(state of buttonStateArray; track state) {
{{state}}
- Floating Button - Floating Button - + + + - Floating Button - Floating Button - + + +
}
`, diff --git a/tedi/components/buttons/button/button.component.scss b/tedi/components/buttons/button/button.component.scss index 9bc60658e..2d930f5ee 100644 --- a/tedi/components/buttons/button/button.component.scss +++ b/tedi/components/buttons/button/button.component.scss @@ -3,46 +3,53 @@ $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; -@mixin button-variant-color-vars($variant, $icon-only: false) { +@mixin button-variant-color-vars($variant, $icon-only: false, $affix: main) { & { @if $icon-only { - --_btn-bg: var(--button-main-#{$variant}-icon-only-background-default); + --_btn-bg: var( + --button-#{$affix}-#{$variant}-icon-only-background-default + ); } @else { - --_btn-bg: var(--button-main-#{$variant}-background-default); + --_btn-bg: var(--button-#{$affix}-#{$variant}-background-default); } - --_btn-border: var(--button-main-#{$variant}-border-default); - --_btn-text: var(--button-main-#{$variant}-text-default); + --_btn-border: var(--button-#{$affix}-#{$variant}-border-default); + --_btn-text: var(--button-#{$affix}-#{$variant}-text-default); --_btn-outline: var(--primary-500); - @include button-state-color-vars($variant, "hover", $icon-only); - @include button-state-color-vars($variant, "focus", $icon-only); - @include button-state-color-vars($variant, "active", $icon-only); + @include button-state-color-vars($variant, "hover", $icon-only, $affix); + @include button-state-color-vars($variant, "focus", $icon-only, $affix); + @include button-state-color-vars($variant, "active", $icon-only, $affix); @include button-disabled-color-vars($variant); } } -@mixin button-state-color-vars($variant, $state, $icon-only: false) { +@mixin button-state-color-vars( + $variant, + $state, + $icon-only: false, + $affix: main +) { & { @if $icon-only { --_btn-#{$state}-bg: var( - --button-main-#{$variant}-icon-only-background-#{$state}, - var(--button-main-#{$variant}-icon-only-background-default) + --button-#{$affix}-#{$variant}-icon-only-background-#{$state}, + var(--button-#{$affix}-#{$variant}-icon-only-background-default) ); } @else { --_btn-#{$state}-bg: var( - --button-main-#{$variant}-background-#{$state}, - var(--button-main-#{$variant}-background-default) + --button-#{$affix}-#{$variant}-background-#{$state}, + var(--button-#{$affix}-#{$variant}-background-default) ); } --_btn-#{$state}-border: var( - --button-main-#{$variant}-border-#{$state}, - var(--button-main-#{$variant}-border-default) + --button-#{$affix}-#{$variant}-border-#{$state}, + var(--button-#{$affix}-#{$variant}-border-default) ); --_btn-#{$state}-text: var( - --button-main-#{$variant}-text-#{$state}, - var(--button-main-#{$variant}-text-default) + --button-#{$affix}-#{$variant}-text-#{$state}, + var(--button-#{$affix}-#{$variant}-text-default) ); } } @@ -136,6 +143,11 @@ $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; @include mixins.responsive-styles(font-size, button-text-size-default); } + &--large { + @include button-size-vars("lg"); + @include mixins.responsive-styles(font-size, button-text-size-default); + } + &--primary { @include button-variant-color-vars("primary"); } diff --git a/tedi/components/buttons/button/button.component.ts b/tedi/components/buttons/button/button.component.ts index 3f5b716a4..86dc01f6d 100644 --- a/tedi/components/buttons/button/button.component.ts +++ b/tedi/components/buttons/button/button.component.ts @@ -20,7 +20,7 @@ export type ButtonVariant = | "secondary-inverted" | "neutral-inverted"; -export type ButtonSize = "default" | "small"; +export type ButtonSize = "default" | "small" | "large"; @Component({ selector: "[tedi-button]", @@ -57,7 +57,7 @@ export class ButtonComponent implements AfterContentChecked { .map((node, index) => ({ node, index })) .filter(x => x.node.nodeType === Node.ELEMENT_NODE && x.node.nodeName === "TEDI-ICON") .map(x => x.index); - + const iconCount = iconIndexes.length; this.iconOnly.set(nodeCount === 1 && iconCount === 1); this.iconFirst.set(iconIndexes.includes(0)); From 0b0a15eb7983daa09b04eca7cb1322f54f06d469 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Tue, 18 Nov 2025 10:39:36 +0200 Subject: [PATCH 13/23] feat(floating-button): re-add box-shadow + add links to storybook #195 --- .../buttons/floating-button/floating-button.component.scss | 4 +--- .../buttons/floating-button/floating-button.stories.ts | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/community/components/buttons/floating-button/floating-button.component.scss b/community/components/buttons/floating-button/floating-button.component.scss index adb5195ec..b45daadb4 100644 --- a/community/components/buttons/floating-button/floating-button.component.scss +++ b/community/components/buttons/floating-button/floating-button.component.scss @@ -5,9 +5,7 @@ buttonMixins; button.tedi-floating-button { - &--horizontal { - @include mixins.responsive-styles(border-radius, button-radius-default); - } + box-shadow: 0 4px 10px 0 var(--alpha-14); &--vertical { min-width: max-content; diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index 1514c7d33..ab14524b3 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -16,6 +16,10 @@ interface StoryArgs { type StoryFloatingButtonArgs = FloatingButtonComponent & StoryArgs; +/** + * Figma ↗
+ * Zeroheight ↗
+ **/ const meta: Meta = { title: "Community/Buttons/Floating Button", component: FloatingButtonComponent, From 4b3715f29cc4d37375de4e23dc4fe48cb55f12b9 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Tue, 18 Nov 2025 10:46:41 +0200 Subject: [PATCH 14/23] feat(floating-button): use default size by default in storybook #195 --- .../buttons/floating-button/floating-button.stories.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index ab14524b3..a2ab2a99d 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -33,6 +33,7 @@ const meta: Meta = { variant: "primary", axis: "horizontal", textOffset: "30px", + size: "default", }, argTypes: { variant: { From eed60d14d84b0cc47f3dd5b38be120dbf309276f Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:50:06 +0200 Subject: [PATCH 15/23] feat(floating-button): move button implementation to directive to use as shared #195 --- .../floating-button.component.ts | 4 +- .../floating-button.stories.ts | 5 +- .../modal/footer/modal-footer.component.ts | 6 +- .../buttons/button/base-button.directive.ts | 90 +++++++++++++++++++ .../buttons/button/button.component.ts | 89 ++---------------- .../buttons/button/button.stories.ts | 12 ++- 6 files changed, 117 insertions(+), 89 deletions(-) create mode 100644 tedi/components/buttons/button/base-button.directive.ts diff --git a/community/components/buttons/floating-button/floating-button.component.ts b/community/components/buttons/floating-button/floating-button.component.ts index 55846a39c..fcc50f738 100644 --- a/community/components/buttons/floating-button/floating-button.component.ts +++ b/community/components/buttons/floating-button/floating-button.component.ts @@ -22,8 +22,8 @@ export class FloatingButtonComponent extends ButtonComponent { floatClasses = computed(() => { const classes = ["tedi-floating-button"]; classes.push(`tedi-floating-button--${this.axis() ?? "horizontal"}`); - classes.push(`tedi-floating-button--${this.variant() ?? "primary"}`); + // classes.push(`tedi-floating-button--${this.variant() ?? "primary"}`); - return `${classes.join(" ")} ${this.classes()}`; + return classes.join(" "); }); } diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index a2ab2a99d..ed7f89b9e 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -6,6 +6,7 @@ import { } from "@storybook/angular"; import { FloatingButtonComponent } from "./floating-button.component"; import { IconComponent } from "tedi/components"; +import { BaseButtonDirective } from "tedi/components/buttons/button/base-button.directive"; const buttonSizeArray = ["default", "large"]; const buttonStateArray = ["Default", "Hover", "Active", "Focus"]; @@ -14,7 +15,9 @@ interface StoryArgs { textOffset: string; } -type StoryFloatingButtonArgs = FloatingButtonComponent & StoryArgs; +type StoryFloatingButtonArgs = FloatingButtonComponent & + StoryArgs & + BaseButtonDirective; /** * Figma ↗
diff --git a/community/components/overlay/modal/footer/modal-footer.component.ts b/community/components/overlay/modal/footer/modal-footer.component.ts index 1ecfc13ad..ba86d644f 100644 --- a/community/components/overlay/modal/footer/modal-footer.component.ts +++ b/community/components/overlay/modal/footer/modal-footer.component.ts @@ -2,12 +2,14 @@ import { NgStyle } from "@angular/common"; import { Component, inject, model, OnInit } from "@angular/core"; import { ButtonComponent, - ButtonSize, - ButtonVariant, IconComponent, } from "@tedi-design-system/angular/tedi"; import { DialogData } from "../modal.component"; import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { + ButtonSize, + ButtonVariant, +} from "tedi/components/buttons/button/base-button.directive"; export interface ModalFooterButton { label: string; diff --git a/tedi/components/buttons/button/base-button.directive.ts b/tedi/components/buttons/button/base-button.directive.ts new file mode 100644 index 000000000..26a5114dc --- /dev/null +++ b/tedi/components/buttons/button/base-button.directive.ts @@ -0,0 +1,90 @@ +import { + Directive, + AfterContentChecked, + signal, + input, + inject, + ElementRef, + computed, +} from "@angular/core"; + +export type ButtonVariant = + | "primary" + | "secondary" + | "neutral" + | "success" + | "danger" + | "danger-neutral" + | "primary-inverted" + | "secondary-inverted" + | "neutral-inverted"; + +export type ButtonSize = "default" | "small" | "large"; + +@Directive({ + host: { + "[class]": "classes()", + }, +}) +export class BaseButtonDirective implements AfterContentChecked { + /** + * Specifies the color theme of the button. The color should meet accessibility standards for color contrast. + * @default primary + */ + variant = input("primary"); + /** + * Defines the size of the button. + * @default default + */ + size = input("default"); + + private host = inject(ElementRef); + iconOnly = signal(false); + iconFirst = signal(false); + iconLast = signal(false); + + ngAfterContentChecked(): void { + const hostElement = this.host.nativeElement as HTMLElement; + const nodes = Array.from(hostElement.childNodes).filter( + (node) => + node.nodeType === Node.ELEMENT_NODE || + (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) + ); + const nodeCount = nodes.length; + const iconIndexes = nodes + .map((node, index) => ({ node, index })) + .filter( + (x) => + x.node.nodeType === Node.ELEMENT_NODE && + x.node.nodeName === "TEDI-ICON" + ) + .map((x) => x.index); + + const iconCount = iconIndexes.length; + this.iconOnly.set(nodeCount === 1 && iconCount === 1); + this.iconFirst.set(iconIndexes.includes(0)); + this.iconLast.set(iconIndexes.includes(nodes.length - 1)); + } + + classes = computed(() => { + const classList = [ + "tedi-button", + `tedi-button--${this.variant()}`, + `tedi-button--${this.size()}`, + ]; + + if (this.iconOnly()) { + classList.push("tedi-button--icon-only"); + } + + if (!this.iconFirst()) { + classList.push("tedi-button--pl"); + } + + if (!this.iconLast()) { + classList.push("tedi-button--pr"); + } + + return classList.join(" "); + }); +} diff --git a/tedi/components/buttons/button/button.component.ts b/tedi/components/buttons/button/button.component.ts index 86dc01f6d..d6016860e 100644 --- a/tedi/components/buttons/button/button.component.ts +++ b/tedi/components/buttons/button/button.component.ts @@ -1,26 +1,5 @@ -import { - Component, - computed, - ElementRef, - inject, - input, - signal, - ViewEncapsulation, - AfterContentChecked, -} from "@angular/core"; - -export type ButtonVariant = - | "primary" - | "secondary" - | "neutral" - | "success" - | "danger" - | "danger-neutral" - | "primary-inverted" - | "secondary-inverted" - | "neutral-inverted"; - -export type ButtonSize = "default" | "small" | "large"; +import { Component, ViewEncapsulation } from "@angular/core"; +import { BaseButtonDirective } from "./base-button.directive"; @Component({ selector: "[tedi-button]", @@ -28,61 +7,11 @@ export type ButtonSize = "default" | "small" | "large"; template: "", styleUrl: "./button.component.scss", encapsulation: ViewEncapsulation.None, - host: { - "[class]": "classes()", - }, + hostDirectives: [ + { + directive: BaseButtonDirective, + inputs: ["variant", "size"], + }, + ], }) -export class ButtonComponent implements AfterContentChecked { - /** - * Specifies the color theme of the button. The color should meet accessibility standards for color contrast. - * @default primary - */ - variant = input("primary"); - /** - * Defines the size of the button. - * @default default - */ - size = input("default"); - - private host = inject(ElementRef); - iconOnly = signal(false); - iconFirst = signal(false); - iconLast = signal(false); - - ngAfterContentChecked(): void { - const hostElement = this.host.nativeElement as HTMLElement; - const nodes = Array.from(hostElement.childNodes).filter(node => node.nodeType === Node.ELEMENT_NODE || (node.nodeType === Node.TEXT_NODE && node.textContent?.trim())); - const nodeCount = nodes.length; - const iconIndexes = nodes - .map((node, index) => ({ node, index })) - .filter(x => x.node.nodeType === Node.ELEMENT_NODE && x.node.nodeName === "TEDI-ICON") - .map(x => x.index); - - const iconCount = iconIndexes.length; - this.iconOnly.set(nodeCount === 1 && iconCount === 1); - this.iconFirst.set(iconIndexes.includes(0)); - this.iconLast.set(iconIndexes.includes(nodes.length - 1)); - } - - classes = computed(() => { - const classList = [ - "tedi-button", - `tedi-button--${this.variant()}`, - `tedi-button--${this.size()}`, - ]; - - if (this.iconOnly()) { - classList.push("tedi-button--icon-only"); - } - - if (!this.iconFirst()) { - classList.push("tedi-button--pl"); - } - - if (!this.iconLast()) { - classList.push("tedi-button--pr"); - } - - return classList.join(" "); - }); -} +export class ButtonComponent {} diff --git a/tedi/components/buttons/button/button.stories.ts b/tedi/components/buttons/button/button.stories.ts index a1dfcb932..7b183b58f 100644 --- a/tedi/components/buttons/button/button.stories.ts +++ b/tedi/components/buttons/button/button.stories.ts @@ -10,6 +10,7 @@ import { TextColor, TextComponent } from "../../base/text/text.component"; import { RowComponent } from "../../helpers/grid/row/row.component"; import { ColComponent } from "../../helpers/grid/col/col.component"; import { IconComponent } from "../../base/icon/icon.component"; +import { BaseButtonDirective } from "./base-button.directive"; const PSEUDO_STATE = ["Default", "Hover", "Active", "Focus", "Disabled"]; @@ -78,9 +79,11 @@ export default { }, }, }, -} as Meta; +} as Meta; -type ButtonType = ButtonComponent & { ngContent: string }; +type StoryButtonType = ButtonComponent & BaseButtonDirective; + +type ButtonType = StoryButtonType & { ngContent: string }; export const Default: StoryObj = { args: { @@ -92,7 +95,8 @@ export const Default: StoryObj = { }), }; -type TemplateType = ButtonComponent & { titleColor?: TextColor }; +type TemplateType = StoryButtonType & + BaseButtonDirective & { titleColor?: TextColor }; const ButtonTemplate: StoryFn = ({ titleColor = "primary", @@ -147,7 +151,7 @@ const ButtonTemplate: StoryFn = ({ `, }); -type ButtonStory = StoryObj; +type ButtonStory = StoryObj; export const Primary: StoryObj = { parameters: { From 881ee6da87b1de014c35b9f42a124842c8c48fff Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:58:05 +0200 Subject: [PATCH 16/23] feat(floating-button): remove unused commented code #195 --- .../buttons/floating-button/floating-button.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/community/components/buttons/floating-button/floating-button.component.ts b/community/components/buttons/floating-button/floating-button.component.ts index fcc50f738..a5ac9336b 100644 --- a/community/components/buttons/floating-button/floating-button.component.ts +++ b/community/components/buttons/floating-button/floating-button.component.ts @@ -22,7 +22,6 @@ export class FloatingButtonComponent extends ButtonComponent { floatClasses = computed(() => { const classes = ["tedi-floating-button"]; classes.push(`tedi-floating-button--${this.axis() ?? "horizontal"}`); - // classes.push(`tedi-floating-button--${this.variant() ?? "primary"}`); return classes.join(" "); }); From e0229be9625fb96b928d3475d6902549d74afba2 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Wed, 19 Nov 2025 13:59:16 +0200 Subject: [PATCH 17/23] feat(floating-button): fix tesst #195 --- tedi/components/buttons/button/button.component.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tedi/components/buttons/button/button.component.spec.ts b/tedi/components/buttons/button/button.component.spec.ts index 67225b3d6..dd9d2380c 100644 --- a/tedi/components/buttons/button/button.component.spec.ts +++ b/tedi/components/buttons/button/button.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { ButtonComponent, ButtonSize, ButtonVariant } from "./button.component"; import { IconComponent } from "../../base/icon/icon.component"; +import { ButtonSize, ButtonVariant } from "./base-button.directive"; +import { ButtonComponent } from "./button.component"; describe("ButtonComponent", () => { let fixture: ComponentFixture; From 094fb594f86ca42518a7ede03224a11918445a6f Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:00:19 +0200 Subject: [PATCH 18/23] feat(floating-button): export base-button directive in index file #195 --- tedi/components/buttons/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tedi/components/buttons/index.ts b/tedi/components/buttons/index.ts index 119c4d2ed..6b03de438 100644 --- a/tedi/components/buttons/index.ts +++ b/tedi/components/buttons/index.ts @@ -2,3 +2,4 @@ export * from "./button/button.component"; export * from "./info-button/info-button.component"; export * from "./collapse/collapse.component"; export * from "./closing-button/closing-button.component"; +export * from "./button/base-button.directive"; From 09e98d68a963693ff2d76752a2fbed8c6ebda307 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Wed, 19 Nov 2025 15:57:09 +0200 Subject: [PATCH 19/23] feat(floating-button): move inputs into button and floating button comp + narrower types #195 --- .../floating-button.component.scss | 8 +++++ .../floating-button.component.ts | 31 ++++++++++++++++--- .../floating-button.stories.ts | 5 +-- .../modal/footer/modal-footer.component.ts | 6 ++-- .../buttons/button/base-button.directive.ts | 31 +------------------ .../buttons/button/button.component.spec.ts | 3 +- .../buttons/button/button.component.ts | 29 +++++++++++++++-- .../buttons/button/button.stories.ts | 8 ++--- 8 files changed, 68 insertions(+), 53 deletions(-) diff --git a/community/components/buttons/floating-button/floating-button.component.scss b/community/components/buttons/floating-button/floating-button.component.scss index b45daadb4..3f1b6e1ce 100644 --- a/community/components/buttons/floating-button/floating-button.component.scss +++ b/community/components/buttons/floating-button/floating-button.component.scss @@ -21,6 +21,14 @@ button.tedi-floating-button { @include mixins.responsive-styles(border-top-left-radius, button-radius-sm); } + &.tedi-floating-button--primary { + @include buttonMixins.button-variant-color-vars( + "primary", + false, + "floating" + ); + } + &.tedi-floating-button--secondary { @include buttonMixins.button-variant-color-vars( "secondary", diff --git a/community/components/buttons/floating-button/floating-button.component.ts b/community/components/buttons/floating-button/floating-button.component.ts index a5ac9336b..80eee9898 100644 --- a/community/components/buttons/floating-button/floating-button.component.ts +++ b/community/components/buttons/floating-button/floating-button.component.ts @@ -1,6 +1,9 @@ import { Component, computed, input, ViewEncapsulation } from "@angular/core"; -import { ButtonComponent } from "tedi/components"; +import { BaseButtonDirective } from "tedi/components"; +export type FloatingButtonVariant = "primary" | "secondary"; + +export type FloatingButtonSize = "default" | "large"; export type FloatingButtonAxis = "horizontal" | "vertical"; @Component({ @@ -8,11 +11,26 @@ export type FloatingButtonAxis = "horizontal" | "vertical"; template: ``, styleUrl: "./floating-button.component.scss", encapsulation: ViewEncapsulation.None, + hostDirectives: [ + { + directive: BaseButtonDirective, + }, + ], host: { "[class]": "floatClasses()", }, }) -export class FloatingButtonComponent extends ButtonComponent { +export class FloatingButtonComponent { + /** + * Specifies the color theme of the button. The color should meet accessibility standards for color contrast. + * @default primary + */ + variant = input("primary"); + /** + * Defines the size of the button. + * @default default + */ + size = input("default"); /** * Button axis * @default horizontal @@ -20,9 +38,12 @@ export class FloatingButtonComponent extends ButtonComponent { axis = input("horizontal"); floatClasses = computed(() => { - const classes = ["tedi-floating-button"]; - classes.push(`tedi-floating-button--${this.axis() ?? "horizontal"}`); - + const classes = [ + "tedi-floating-button", + `tedi-button--${this.variant() ?? "primary"}`, + `tedi-button--${this.size() ?? "default"}`, + `tedi-floating-button--${this.axis() ?? "horizontal"}`, + ]; return classes.join(" "); }); } diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index ed7f89b9e..a2ab2a99d 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -6,7 +6,6 @@ import { } from "@storybook/angular"; import { FloatingButtonComponent } from "./floating-button.component"; import { IconComponent } from "tedi/components"; -import { BaseButtonDirective } from "tedi/components/buttons/button/base-button.directive"; const buttonSizeArray = ["default", "large"]; const buttonStateArray = ["Default", "Hover", "Active", "Focus"]; @@ -15,9 +14,7 @@ interface StoryArgs { textOffset: string; } -type StoryFloatingButtonArgs = FloatingButtonComponent & - StoryArgs & - BaseButtonDirective; +type StoryFloatingButtonArgs = FloatingButtonComponent & StoryArgs; /** * Figma ↗
diff --git a/community/components/overlay/modal/footer/modal-footer.component.ts b/community/components/overlay/modal/footer/modal-footer.component.ts index ba86d644f..1ecfc13ad 100644 --- a/community/components/overlay/modal/footer/modal-footer.component.ts +++ b/community/components/overlay/modal/footer/modal-footer.component.ts @@ -2,14 +2,12 @@ import { NgStyle } from "@angular/common"; import { Component, inject, model, OnInit } from "@angular/core"; import { ButtonComponent, + ButtonSize, + ButtonVariant, IconComponent, } from "@tedi-design-system/angular/tedi"; import { DialogData } from "../modal.component"; import { DIALOG_DATA } from "@angular/cdk/dialog"; -import { - ButtonSize, - ButtonVariant, -} from "tedi/components/buttons/button/base-button.directive"; export interface ModalFooterButton { label: string; diff --git a/tedi/components/buttons/button/base-button.directive.ts b/tedi/components/buttons/button/base-button.directive.ts index 26a5114dc..786bb28c6 100644 --- a/tedi/components/buttons/button/base-button.directive.ts +++ b/tedi/components/buttons/button/base-button.directive.ts @@ -2,42 +2,17 @@ import { Directive, AfterContentChecked, signal, - input, inject, ElementRef, computed, } from "@angular/core"; -export type ButtonVariant = - | "primary" - | "secondary" - | "neutral" - | "success" - | "danger" - | "danger-neutral" - | "primary-inverted" - | "secondary-inverted" - | "neutral-inverted"; - -export type ButtonSize = "default" | "small" | "large"; - @Directive({ host: { "[class]": "classes()", }, }) export class BaseButtonDirective implements AfterContentChecked { - /** - * Specifies the color theme of the button. The color should meet accessibility standards for color contrast. - * @default primary - */ - variant = input("primary"); - /** - * Defines the size of the button. - * @default default - */ - size = input("default"); - private host = inject(ElementRef); iconOnly = signal(false); iconFirst = signal(false); @@ -67,11 +42,7 @@ export class BaseButtonDirective implements AfterContentChecked { } classes = computed(() => { - const classList = [ - "tedi-button", - `tedi-button--${this.variant()}`, - `tedi-button--${this.size()}`, - ]; + const classList = ["tedi-button"]; if (this.iconOnly()) { classList.push("tedi-button--icon-only"); diff --git a/tedi/components/buttons/button/button.component.spec.ts b/tedi/components/buttons/button/button.component.spec.ts index dd9d2380c..46dceced1 100644 --- a/tedi/components/buttons/button/button.component.spec.ts +++ b/tedi/components/buttons/button/button.component.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { IconComponent } from "../../base/icon/icon.component"; -import { ButtonSize, ButtonVariant } from "./base-button.directive"; -import { ButtonComponent } from "./button.component"; +import { ButtonComponent, ButtonSize, ButtonVariant } from "./button.component"; describe("ButtonComponent", () => { let fixture: ComponentFixture; diff --git a/tedi/components/buttons/button/button.component.ts b/tedi/components/buttons/button/button.component.ts index d6016860e..a99827b76 100644 --- a/tedi/components/buttons/button/button.component.ts +++ b/tedi/components/buttons/button/button.component.ts @@ -1,6 +1,19 @@ -import { Component, ViewEncapsulation } from "@angular/core"; +import { Component, input, ViewEncapsulation } from "@angular/core"; import { BaseButtonDirective } from "./base-button.directive"; +export type ButtonVariant = + | "primary" + | "secondary" + | "neutral" + | "success" + | "danger" + | "danger-neutral" + | "primary-inverted" + | "secondary-inverted" + | "neutral-inverted"; + +export type ButtonSize = "default" | "small"; + @Component({ selector: "[tedi-button]", standalone: true, @@ -10,8 +23,18 @@ import { BaseButtonDirective } from "./base-button.directive"; hostDirectives: [ { directive: BaseButtonDirective, - inputs: ["variant", "size"], }, ], }) -export class ButtonComponent {} +export class ButtonComponent { + /** + * Specifies the color theme of the button. The color should meet accessibility standards for color contrast. + * @default primary + */ + variant = input("primary"); + /** + * Defines the size of the button. + * @default default + */ + size = input("default"); +} diff --git a/tedi/components/buttons/button/button.stories.ts b/tedi/components/buttons/button/button.stories.ts index 7b183b58f..3d240dd41 100644 --- a/tedi/components/buttons/button/button.stories.ts +++ b/tedi/components/buttons/button/button.stories.ts @@ -81,9 +81,7 @@ export default { }, } as Meta; -type StoryButtonType = ButtonComponent & BaseButtonDirective; - -type ButtonType = StoryButtonType & { ngContent: string }; +type ButtonType = ButtonComponent & { ngContent: string }; export const Default: StoryObj = { args: { @@ -95,7 +93,7 @@ export const Default: StoryObj = { }), }; -type TemplateType = StoryButtonType & +type TemplateType = ButtonType & BaseButtonDirective & { titleColor?: TextColor }; const ButtonTemplate: StoryFn = ({ @@ -151,7 +149,7 @@ const ButtonTemplate: StoryFn = ({ `, }); -type ButtonStory = StoryObj; +type ButtonStory = StoryObj; export const Primary: StoryObj = { parameters: { From 0b65dd878ffcf5df5ac1eff0f74f09e18430c943 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:19:59 +0200 Subject: [PATCH 20/23] feat(floating-button): ensure to not use non encapsulated styles in floating button #195 --- .../floating-button.component.scss | 10 +- .../floating-button.component.ts | 22 ++- .../floating-button.stories.ts | 4 +- .../buttons/button/base-button.directive.ts | 13 +- .../buttons/button/button.component.scss | 135 +++++++++--------- .../buttons/button/button.component.ts | 14 +- 6 files changed, 121 insertions(+), 77 deletions(-) diff --git a/community/components/buttons/floating-button/floating-button.component.scss b/community/components/buttons/floating-button/floating-button.component.scss index 3f1b6e1ce..1ffa4c11f 100644 --- a/community/components/buttons/floating-button/floating-button.component.scss +++ b/community/components/buttons/floating-button/floating-button.component.scss @@ -1,10 +1,10 @@ @use "@tedi-design-system/core/mixins"; @use "@tedi-design-system/core/bootstrap-utility/breakpoints"; -@forward "../../../../tedi/components/buttons/button/button.component.scss"; @use "../../../../tedi/components/buttons/button/button.component.scss" as buttonMixins; button.tedi-floating-button { + @include buttonMixins.button-main-styles(); box-shadow: 0 4px 10px 0 var(--alpha-14); &--vertical { @@ -36,4 +36,12 @@ button.tedi-floating-button { "floating" ); } + + &--default { + @include buttonMixins.button-size("md"); + } + + &--large { + @include buttonMixins.button-size("lg"); + } } diff --git a/community/components/buttons/floating-button/floating-button.component.ts b/community/components/buttons/floating-button/floating-button.component.ts index 80eee9898..ea0d8b060 100644 --- a/community/components/buttons/floating-button/floating-button.component.ts +++ b/community/components/buttons/floating-button/floating-button.component.ts @@ -1,4 +1,11 @@ -import { Component, computed, input, ViewEncapsulation } from "@angular/core"; +import { + Component, + computed, + input, + ViewEncapsulation, + OnInit, + inject, +} from "@angular/core"; import { BaseButtonDirective } from "tedi/components"; export type FloatingButtonVariant = "primary" | "secondary"; @@ -20,7 +27,7 @@ export type FloatingButtonAxis = "horizontal" | "vertical"; "[class]": "floatClasses()", }, }) -export class FloatingButtonComponent { +export class FloatingButtonComponent implements OnInit { /** * Specifies the color theme of the button. The color should meet accessibility standards for color contrast. * @default primary @@ -37,11 +44,18 @@ export class FloatingButtonComponent { */ axis = input("horizontal"); + buttonDirective = inject(BaseButtonDirective); + + ngOnInit() { + this.buttonDirective.classNamePrefix.set("tedi-floating-button"); + console.log("set", this.buttonDirective); + } + floatClasses = computed(() => { const classes = [ "tedi-floating-button", - `tedi-button--${this.variant() ?? "primary"}`, - `tedi-button--${this.size() ?? "default"}`, + `tedi-floating-button--${this.variant() ?? "primary"}`, + `tedi-floating-button--${this.size() ?? "default"}`, `tedi-floating-button--${this.axis() ?? "horizontal"}`, ]; return classes.join(" "); diff --git a/community/components/buttons/floating-button/floating-button.stories.ts b/community/components/buttons/floating-button/floating-button.stories.ts index a2ab2a99d..b93ee7539 100644 --- a/community/components/buttons/floating-button/floating-button.stories.ts +++ b/community/components/buttons/floating-button/floating-button.stories.ts @@ -39,7 +39,7 @@ const meta: Meta = { variant: { control: "select", description: "Specifies the color theme of the button.", - options: ["primary", "secondary"], + options: ["primary", "secondary", "neutral", "success"], }, axis: { control: "radio", @@ -49,7 +49,7 @@ const meta: Meta = { size: { control: "radio", description: "Button size.", - options: ["small", "default", "large"], + options: ["default", "large"], }, // not meant to be user-editable or seen textOffset: { diff --git a/tedi/components/buttons/button/base-button.directive.ts b/tedi/components/buttons/button/base-button.directive.ts index 786bb28c6..5dbfde55f 100644 --- a/tedi/components/buttons/button/base-button.directive.ts +++ b/tedi/components/buttons/button/base-button.directive.ts @@ -13,6 +13,11 @@ import { }, }) export class BaseButtonDirective implements AfterContentChecked { + /** + * CSS class name affix the directive should provide + */ + classNamePrefix = signal("tedi-button"); + private host = inject(ElementRef); iconOnly = signal(false); iconFirst = signal(false); @@ -42,18 +47,18 @@ export class BaseButtonDirective implements AfterContentChecked { } classes = computed(() => { - const classList = ["tedi-button"]; + const classList = [this.classNamePrefix()]; if (this.iconOnly()) { - classList.push("tedi-button--icon-only"); + classList.push(`${this.classNamePrefix()}--icon-only`); } if (!this.iconFirst()) { - classList.push("tedi-button--pl"); + classList.push(`${this.classNamePrefix()}--pl`); } if (!this.iconLast()) { - classList.push("tedi-button--pr"); + classList.push(`${this.classNamePrefix()}--pr`); } return classList.join(" "); diff --git a/tedi/components/buttons/button/button.component.scss b/tedi/components/buttons/button/button.component.scss index 2d930f5ee..627a0681d 100644 --- a/tedi/components/buttons/button/button.component.scss +++ b/tedi/components/buttons/button/button.component.scss @@ -110,7 +110,7 @@ $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; } } -.tedi-button { +@mixin button-main-styles { display: inline-flex; align-items: center; justify-content: center; @@ -133,20 +133,81 @@ $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; @include mixins.responsive-styles(line-height, body-bold-line-height); @include mixins.responsive-styles(border-radius, button-radius-default); + &--icon-only { + @include mixins.responsive-styles(width, button-md-icon-size); + @include mixins.responsive-styles(height, button-md-icon-size); + @include mixins.responsive-styles(padding, button-md-icon-padding); + } + + &--pl { + &:not(.tedi-button--neutral):not(.tedi-button--danger-neutral):not( + .tedi-button--neutral-inverted + ) { + padding-left: calc( + var(--_btn-padding-x) + var(--_btn-inner-spacing) - 1px + ); + } + } + + &--pr { + &:not(.tedi-button--neutral):not(.tedi-button--danger-neutral):not( + .tedi-button--neutral-inverted + ) { + padding-right: calc( + var(--_btn-padding-x) + var(--_btn-inner-spacing) - 1px + ); + } + } + + &:hover { + background: var(--_btn-hover-bg); + border-color: var(--_btn-hover-border); + color: var(--_btn-hover-text); + } + + &:active { + background: var(--_btn-active-bg); + border-color: var(--_btn-active-border); + color: var(--_btn-active-text); + } + + &:focus-visible { + background: var(--_btn-focus-bg); + border-color: var(--_btn-focus-border); + color: var(--_btn-focus-text); + outline: 2px solid var(--_btn-outline); + outline-offset: 1px; + } + + &:disabled { + background: var(--_btn-disabled-bg); + border-color: var(--_btn-disabled-border); + color: var(--_btn-disabled-text); + cursor: not-allowed; + } + + tedi-icon { + color: inherit; + font-size: inherit; + } +} + +@mixin button-size($size) { + @include button-size-vars(#{$size}); + @include mixins.responsive-styles(font-size, button-text-size-#{$size}); +} + +.tedi-button { + @include button-main-styles(); + &--small { - @include button-size-vars("sm"); - @include mixins.responsive-styles(font-size, button-text-size-sm); + @include button-size("sm"); } &--default { - @include button-size-vars("md"); - @include mixins.responsive-styles(font-size, button-text-size-default); + @include button-size("md"); } - &--large { - @include button-size-vars("lg"); - @include mixins.responsive-styles(font-size, button-text-size-default); - } &--primary { @include button-variant-color-vars("primary"); @@ -188,10 +249,6 @@ $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; } &--icon-only { - @include mixins.responsive-styles(width, button-md-icon-size); - @include mixins.responsive-styles(height, button-md-icon-size); - @include mixins.responsive-styles(padding, button-md-icon-padding); - &.tedi-button--neutral { @include button-variant-color-vars("neutral", true); } @@ -210,56 +267,4 @@ $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; @include mixins.responsive-styles(padding, button-sm-icon-padding); } } - - &--pl { - &:not(.tedi-button--neutral):not(.tedi-button--danger-neutral):not( - .tedi-button--neutral-inverted - ) { - padding-left: calc( - var(--_btn-padding-x) + var(--_btn-inner-spacing) - 1px - ); - } - } - - &--pr { - &:not(.tedi-button--neutral):not(.tedi-button--danger-neutral):not( - .tedi-button--neutral-inverted - ) { - padding-right: calc( - var(--_btn-padding-x) + var(--_btn-inner-spacing) - 1px - ); - } - } - - &:hover { - background: var(--_btn-hover-bg); - border-color: var(--_btn-hover-border); - color: var(--_btn-hover-text); - } - - &:active { - background: var(--_btn-active-bg); - border-color: var(--_btn-active-border); - color: var(--_btn-active-text); - } - - &:focus-visible { - background: var(--_btn-focus-bg); - border-color: var(--_btn-focus-border); - color: var(--_btn-focus-text); - outline: 2px solid var(--_btn-outline); - outline-offset: 1px; - } - - &:disabled { - background: var(--_btn-disabled-bg); - border-color: var(--_btn-disabled-border); - color: var(--_btn-disabled-text); - cursor: not-allowed; - } - - tedi-icon { - color: inherit; - font-size: inherit; - } } diff --git a/tedi/components/buttons/button/button.component.ts b/tedi/components/buttons/button/button.component.ts index a99827b76..91ff42e8c 100644 --- a/tedi/components/buttons/button/button.component.ts +++ b/tedi/components/buttons/button/button.component.ts @@ -1,4 +1,4 @@ -import { Component, input, ViewEncapsulation } from "@angular/core"; +import { Component, computed, input, ViewEncapsulation } from "@angular/core"; import { BaseButtonDirective } from "./base-button.directive"; export type ButtonVariant = @@ -25,6 +25,9 @@ export type ButtonSize = "default" | "small"; directive: BaseButtonDirective, }, ], + host: { + "[class]": "classes()", + }, }) export class ButtonComponent { /** @@ -37,4 +40,13 @@ export class ButtonComponent { * @default default */ size = input("default"); + + classes = computed(() => { + const classList = [ + "tedi-button", + `tedi-button--${this.variant()}`, + `tedi-button--${this.size()}`, + ]; + return classList.join(" "); + }); } From 7f3c5b3b6777f00fc4e0f2c68bcaa8f59109c636 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Thu, 20 Nov 2025 10:24:51 +0200 Subject: [PATCH 21/23] feat(floating-button): remove debug log #195 --- .../buttons/floating-button/floating-button.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/community/components/buttons/floating-button/floating-button.component.ts b/community/components/buttons/floating-button/floating-button.component.ts index ea0d8b060..802796dd5 100644 --- a/community/components/buttons/floating-button/floating-button.component.ts +++ b/community/components/buttons/floating-button/floating-button.component.ts @@ -48,7 +48,6 @@ export class FloatingButtonComponent implements OnInit { ngOnInit() { this.buttonDirective.classNamePrefix.set("tedi-floating-button"); - console.log("set", this.buttonDirective); } floatClasses = computed(() => { From 4b37b131db7325f0168c97ff9edb658a0f174e15 Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:33:10 +0200 Subject: [PATCH 22/23] feat(floating-button): fix scss build warning #195 --- .../floating-button.component.scss | 3 +- .../buttons/button/button.component.scss | 145 +++++++++--------- 2 files changed, 76 insertions(+), 72 deletions(-) diff --git a/community/components/buttons/floating-button/floating-button.component.scss b/community/components/buttons/floating-button/floating-button.component.scss index 1ffa4c11f..09eb8c548 100644 --- a/community/components/buttons/floating-button/floating-button.component.scss +++ b/community/components/buttons/floating-button/floating-button.component.scss @@ -4,9 +4,10 @@ buttonMixins; button.tedi-floating-button { - @include buttonMixins.button-main-styles(); box-shadow: 0 4px 10px 0 var(--alpha-14); + @include buttonMixins.button-main-styles(); + &--vertical { min-width: max-content; white-space: nowrap; diff --git a/tedi/components/buttons/button/button.component.scss b/tedi/components/buttons/button/button.component.scss index 627a0681d..17463dbe8 100644 --- a/tedi/components/buttons/button/button.component.scss +++ b/tedi/components/buttons/button/button.component.scss @@ -111,90 +111,94 @@ $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; } @mixin button-main-styles { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--_btn-inner-spacing); - text-decoration: none; - background: var(--_btn-bg); - color: var(--_btn-text); - border: var(--borders-01) solid var(--_btn-border); - padding: var(--_btn-padding); - transition: 150ms ease; - transition-property: background-color, border-color; - cursor: pointer; - - @include mixins.responsive-styles( - font-family, - family-primary, - $exclude: tablet - ); - @include mixins.responsive-styles(font-weight, body-regular-weight); - @include mixins.responsive-styles(line-height, body-bold-line-height); - @include mixins.responsive-styles(border-radius, button-radius-default); + & { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--_btn-inner-spacing); + text-decoration: none; + background: var(--_btn-bg); + color: var(--_btn-text); + border: var(--borders-01) solid var(--_btn-border); + padding: var(--_btn-padding); + transition: 150ms ease; + transition-property: background-color, border-color; + cursor: pointer; - &--icon-only { - @include mixins.responsive-styles(width, button-md-icon-size); - @include mixins.responsive-styles(height, button-md-icon-size); - @include mixins.responsive-styles(padding, button-md-icon-padding); - } + @include mixins.responsive-styles( + font-family, + family-primary, + $exclude: tablet + ); + @include mixins.responsive-styles(font-weight, body-regular-weight); + @include mixins.responsive-styles(line-height, body-bold-line-height); + @include mixins.responsive-styles(border-radius, button-radius-default); + + &--icon-only { + @include mixins.responsive-styles(width, button-md-icon-size); + @include mixins.responsive-styles(height, button-md-icon-size); + @include mixins.responsive-styles(padding, button-md-icon-padding); + } - &--pl { - &:not(.tedi-button--neutral):not(.tedi-button--danger-neutral):not( - .tedi-button--neutral-inverted - ) { - padding-left: calc( - var(--_btn-padding-x) + var(--_btn-inner-spacing) - 1px - ); + &--pl { + &:not(.tedi-button--neutral):not(.tedi-button--danger-neutral):not( + .tedi-button--neutral-inverted + ) { + padding-left: calc( + var(--_btn-padding-x) + var(--_btn-inner-spacing) - 1px + ); + } } - } - &--pr { - &:not(.tedi-button--neutral):not(.tedi-button--danger-neutral):not( - .tedi-button--neutral-inverted - ) { - padding-right: calc( - var(--_btn-padding-x) + var(--_btn-inner-spacing) - 1px - ); + &--pr { + &:not(.tedi-button--neutral):not(.tedi-button--danger-neutral):not( + .tedi-button--neutral-inverted + ) { + padding-right: calc( + var(--_btn-padding-x) + var(--_btn-inner-spacing) - 1px + ); + } } - } - &:hover { - background: var(--_btn-hover-bg); - border-color: var(--_btn-hover-border); - color: var(--_btn-hover-text); - } + &:hover { + background: var(--_btn-hover-bg); + border-color: var(--_btn-hover-border); + color: var(--_btn-hover-text); + } - &:active { - background: var(--_btn-active-bg); - border-color: var(--_btn-active-border); - color: var(--_btn-active-text); - } + &:active { + background: var(--_btn-active-bg); + border-color: var(--_btn-active-border); + color: var(--_btn-active-text); + } - &:focus-visible { - background: var(--_btn-focus-bg); - border-color: var(--_btn-focus-border); - color: var(--_btn-focus-text); - outline: 2px solid var(--_btn-outline); - outline-offset: 1px; - } + &:focus-visible { + background: var(--_btn-focus-bg); + border-color: var(--_btn-focus-border); + color: var(--_btn-focus-text); + outline: 2px solid var(--_btn-outline); + outline-offset: 1px; + } - &:disabled { - background: var(--_btn-disabled-bg); - border-color: var(--_btn-disabled-border); - color: var(--_btn-disabled-text); - cursor: not-allowed; - } + &:disabled { + background: var(--_btn-disabled-bg); + border-color: var(--_btn-disabled-border); + color: var(--_btn-disabled-text); + cursor: not-allowed; + } - tedi-icon { - color: inherit; - font-size: inherit; + tedi-icon { + color: inherit; + font-size: inherit; + } } } @mixin button-size($size) { - @include button-size-vars(#{$size}); - @include mixins.responsive-styles(font-size, button-text-size-#{$size}); + & { + @include button-size-vars(#{$size}); + @include mixins.responsive-styles(font-size, button-text-size-#{$size}); + } } .tedi-button { @@ -208,7 +212,6 @@ $neutral-variants: "neutral", "neutral-inverted", "danger-neutral"; @include button-size("md"); } - &--primary { @include button-variant-color-vars("primary"); } From 0e80438c77ad53fc79b5e3bc746f665e001b96cb Mon Sep 17 00:00:00 2001 From: Artur Lang <209750178+artur-langl@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:03:04 +0200 Subject: [PATCH 23/23] feat(floating-button): add barrel index eports #195 --- community/components/buttons/floating-button/index.ts | 1 + community/components/buttons/index.ts | 1 + community/index.ts | 1 + 3 files changed, 3 insertions(+) create mode 100644 community/components/buttons/floating-button/index.ts create mode 100644 community/components/buttons/index.ts diff --git a/community/components/buttons/floating-button/index.ts b/community/components/buttons/floating-button/index.ts new file mode 100644 index 000000000..6114a84bf --- /dev/null +++ b/community/components/buttons/floating-button/index.ts @@ -0,0 +1 @@ +export * from "./floating-button.component"; diff --git a/community/components/buttons/index.ts b/community/components/buttons/index.ts new file mode 100644 index 000000000..96e4859ad --- /dev/null +++ b/community/components/buttons/index.ts @@ -0,0 +1 @@ +export * from "./floating-button"; diff --git a/community/index.ts b/community/index.ts index 3c0b3f1ac..fa989c6d1 100644 --- a/community/index.ts +++ b/community/index.ts @@ -1,3 +1,4 @@ +export * from "./components/buttons"; export * from "./components/cards"; export * from "./components/form"; export * from "./components/navigation";