From bd1bf1aba8cf9a25e50f5bfd812fc81c5c32bca6 Mon Sep 17 00:00:00 2001 From: felixw Date: Fri, 22 Dec 2023 11:11:54 +0100 Subject: [PATCH 1/7] fix: wip refactor --- .../src/components/segment/segment.css | 166 +++++++--- .../src/components/segment/segment.tsx | 287 +++++++++++------- .../segmented-button/segmented-button.css | 25 +- .../segmented-button/segmented-button.tsx | 145 ++++----- .../SegmentedButton.stories.mdx | 184 +++++------ 5 files changed, 441 insertions(+), 366 deletions(-) diff --git a/packages/components/src/components/segment/segment.css b/packages/components/src/components/segment/segment.css index 49b7c04896..40133fa1e9 100644 --- a/packages/components/src/components/segment/segment.css +++ b/packages/components/src/components/segment/segment.css @@ -9,7 +9,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -:host { + :host { /* not selected */ --background-color-selected: var(--telekom-color-ui-base); --button-radius: 6px; @@ -17,25 +17,27 @@ --height: 28px; --font: var(--telekom-text-style-caption-bold); --label-disabled: var(--telekom-color-text-and-icon-disabled); - --transition: all var(--telekom-motion-duration-transition) - var(--telekom-motion-easing-standard); --color: var(--telekom-color-text-and-icon-standard); + --transition: all var(--telekom-motion-duration-transition) + var(--telekom-motion-easing-standard); --color-selected: var(--telekom-color-text-and-icon-primary-standard); /* medium styles */ + --font-size-medium: var(--telekom-typography-font-size-body); --font-medium: var(--telekom-text-style-ui-bold); --height-medium: 36px; /* large styles */ + --font-size-large: var(--telekom-typography-font-size-body); --font-large: var(--telekom-text-style-ui-bold); --height-large: 40px; /* hover styles */ - --background-color-hover: var(--telekom-color-ui-state-fill-hovered); + --background-color-hover: var(--telekom-color-ui-state-fill-hovered-inverted); /* active styles */ - --background-color-active: var(--telekom-color-ui-state-fill-pressed); + --background-color-active: var(--telekom-color-ui-state-fill-pressed-inverted); display: flex; align-items: center; @@ -57,6 +59,19 @@ padding: 0; } +.segment--list-item { + list-style: none; + box-sizing: border-box; + position: relative; + border: 0; + width: 100%; + border-radius: var(--button-radius); + /* padding-top: var(--telekom-spacing-composition-space-02); */ + transition: var(--transition); + background-color: transparent; + display: flex; +} + .segment--mask { box-sizing: border-box; display: flex; @@ -68,27 +83,38 @@ border-radius: var(--button-radius); background-color: transparent; font: var(--font); - transition: var(--transition); white-space: nowrap; + padding: 8px; color: var(--color); - line-height: 1; } -button.segment--small.segment--selected .segment--mask { - padding-left: var(--telekom-spacing-composition-space-04); - padding-right: var(--telekom-spacing-composition-space-05); +.segment--icon-only.segment--small .segment--mask { + /* padding-left: var(--telekom-spacing-composition-space-05); + padding-right: var(--telekom-spacing-composition-space-05); */ + padding-left: 0.625rem; + padding-right: 0.625rem; } -button.segment--icon-only.segment--small .segment--mask { +.segment--icon-only.segment--medium .segment--mask { padding-left: var(--telekom-spacing-composition-space-06); padding-right: var(--telekom-spacing-composition-space-06); } -button.segment--small .segment--mask { - padding-left: 0.625rem; - padding-right: 0.625rem; +.segment--icon-only.segment--large .segment--mask { + padding-left: var(--telekom-spacing-composition-space-07); + padding-right: var(--telekom-spacing-composition-space-07); } +.segment--small .segment--mask { + padding-left: 8px; + padding-right: 12px; +} + +/* .segment--multi-select.segment:not(.segment--icon-only) .segment--mask { + padding-left: 19px; + padding-right: 19px; +} */ + .segment--small { height: var(--height); } @@ -97,32 +123,35 @@ button.segment--small .segment--mask { height: var(--height-medium); } -button.segment--medium.segment--selected .segment--mask { - padding-left: var(--telekom-spacing-composition-space-05); - padding-right: var(--telekom-spacing-composition-space-06); +.segment--medium .text-container { + font: var(--font-medium); } -button.segment--medium .segment--mask { - font: var(--font-medium); - padding-left: 0.875rem; - padding-right: 0.875rem; +.segment--medium .segment--mask { + padding-left: var(--telekom-spacing-composition-space-05); + padding-right: var(--telekom-spacing-composition-space-06); } .segment--large { height: var(--height-large); } +.segment--large .text-container { + font: var(--font-large) +} -button.segment--large.segment--selected .segment--mask { +.segment--large .segment--mask { padding-left: var(--telekom-spacing-composition-space-06); padding-right: var(--telekom-spacing-composition-space-07); } -button.segment--large .segment--mask { - font: var(--font-large); - padding-left: var(--telekom-spacing-composition-space-06); - padding-right: var(--telekom-spacing-composition-space-07); + +.segment--icon-only .segment--mask { + padding-left: var(--telekom-spacing-composition-space-05); + padding-right: var(--telekom-spacing-composition-space-05); } +/* selected */ + .segment--selected { background-color: var(--background-color-selected); box-shadow: var(--telekom-shadow-raised-standard); @@ -132,6 +161,8 @@ button.segment--large .segment--mask { color: var(--color-selected); } +/* disabled */ + .segment--disabled.segment--selected .segment--mask { color: var(--label-disabled); } @@ -141,7 +172,9 @@ button.segment--large .segment--mask { background-color: transparent; } -.segment--left-sibling-selected { +/* shadows left and right */ + +.segment--list-item--left-sibling-selected { border-radius: 0 var(--button-radius) var(--button-radius) 0; margin-left: 0; clip-path: inset(-2.75rem -2.75rem -2.75rem 0); @@ -149,7 +182,7 @@ button.segment--large .segment--mask { overflow: hidden; } -.segment--right-sibling-selected { +.segment--list-item--right-sibling-selected { border-radius: var(--button-radius) 0 0 var(--button-radius); clip-path: inset(-2.75rem 0 -2.75rem -2.75rem); margin-right: 0; @@ -158,7 +191,7 @@ button.segment--large .segment--mask { position: relative; } -.segment--left-right-sibling-selected { +.segment--list-item--left-right-sibling-selected { clip-path: inset(-2.75rem 0 -2.75rem 0); border-radius: 0; margin-left: 0; @@ -183,40 +216,68 @@ button.segment--large .segment--mask { position: relative; } -.segment:not(.segment--selected):focus { - outline-offset: 0; -} - .segment--left-right-sibling-selected:focus, .segment--right-sibling-selected:focus, .segment--left-sibling-selected:focus { clip-path: none; } -/* FIXME remove the hard-coded icon names */ + /* icon placement */ + .segment:not(.segment--icon-only) scale-icon-action-checkmark, .segment scale-icon-action-close { margin-right: var(--telekom-spacing-composition-space-03); } -.segment:not(.segment--selected) scale-icon-action-checkmark { - visibility: hidden !important; +/* .success-icon-container { + margin-right: 4px; +} */ + +.segment--large .success-icon-container { + margin-right: 6px; } -.segment scale-icon-action-checkmark { - vertical-align: middle; +.segment:not(.segment--selected) .success-icon-container { + display: none; +} + +.segment:not(.segment--selected):not(.segment--icon-text).segment:not(.segment--icon-only) .text-container { + margin-left: 18px; + /* border: 1px solid purple; */ +} + +.segment--medium:not(.segment--selected):not(.segment--icon-text).segment:not(.segment--icon-only) .text-container { + margin-left: 20px; +} + +.segment--large:not(.segment--selected):not(.segment--icon-text).segment:not(.segment--icon-only) .text-container { + margin-left: 26px; +} + +.segment--selected .text-container { + margin-left: 0; } .segment--selected:not(.segment--icon-only) .icon-container { - display: none !important; + visibility: hidden; } -.segment .success-icon-container { +.segment--icon-only .success-icon-container, +.segment--icon-text .success-icon-container { display: none; } -.segment--selected .success-icon-container { +.segment--icon-text .success-icon-container{ + display: none; +} + +.segment.segment--selected:not(.segment--icon-only) .icon-container { + display: none; +} + +.segment--selected:not(.segment--icon-only) .success-icon-container { display: flex; + visibility: visible; justify-content: center; align-items: center; } @@ -231,3 +292,26 @@ button.segment--large .segment--mask { .segment--selected .icon-container { color: var(--color-selected); } + +.segment:not(.segment--selected).segment:not(.segment--icon-only):not(.segment--icon-text) .text-container { + transform: translateX(-9px); +} + +.segment.segment--large:not(.segment--selected).segment:not(.segment--icon-only):not(.segment--icon-text) .text-container { + transform: translateX(-13px); +} + +.segment--icon-text .icon-container ::slotted(*){ + margin-right: var(--telekom-spacing-composition-space-03); +} + + +.segment--input { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} + + +.segment--list-item--left-sibling-selected { +} \ No newline at end of file diff --git a/packages/components/src/components/segment/segment.tsx b/packages/components/src/components/segment/segment.tsx index c2daf67c0c..e6e9761923 100644 --- a/packages/components/src/components/segment/segment.tsx +++ b/packages/components/src/components/segment/segment.tsx @@ -18,12 +18,20 @@ import { Event, EventEmitter, Method, + Watch, } from '@stencil/core'; import classNames from 'classnames'; import { emitEvent } from '../../utils/utils'; +import { ScaleIcon, isScaleIcon } from '../../utils/utils'; let i = 0; +const iconSizeMap = { + small: 14, + medium: 16, + large: 20, +}; + @Component({ tag: 'scale-segment', styleUrl: 'segment.css', @@ -31,17 +39,17 @@ let i = 0; }) export class Segment { @Element() hostElement: HTMLElement; - /** (optional) The size of the segment */ + /** (optional) The size of the button */ @Prop() size?: 'small' | 'medium' | 'large' = 'small'; - /** (optional) If `true`, the segment is selected */ + /** (optional) If `true`, the button is selected */ @Prop({ mutable: true }) selected?: boolean = false; - /** (optional) If `true`, the segment is disabled */ + /** (optional) If `true`, the button is disabled */ @Prop() disabled?: boolean = false; /** (optional) segment's id */ @Prop({ reflect: true, mutable: true }) segmentId?: string; - /** (optional) aria-label attribute needed for icon-only segments */ + /** (optional) aria-label attribute needed for icon-only buttons */ @Prop() ariaLabelSegment: string; - /** (optional) Segment width set to ensure that all segments have the same width */ + /** (optional) Button width set to ensure that all buttons have the same width */ @Prop() width?: string; /** (optional) Injected CSS styles */ @Prop() styles?: string; @@ -58,15 +66,12 @@ export class Segment { @Prop() ariaDescriptionTranslation = '$selected'; /** (optional) position within group */ @Prop() position?: number; - /** (optional) position within group */ - @Prop({ mutable: true }) hasIcon?: boolean; - /** (optional) position within group */ - @Prop({ mutable: true }) textOnly?: boolean; - /** (optional) position within group */ - @Prop({ mutable: true }) iconOnly?: boolean; - /** (optional) the index of the currently selected segment in the segmented-button */ - @Prop({ mutable: true }) selectedIndex?: string; - + /** (optional) icon only segment */ + @Prop({ mutable: true }) iconOnly?: boolean = false; + /** (optional) multi select segment */ + @Prop({ mutable: true }) multiSelect?: boolean = false; + /** (optional) segment with icon and text */ + @Prop({ mutable: true }) iconText?: boolean = false; /** Emitted when button is clicked */ @Event({ eventName: 'scale-click' }) scaleClick!: EventEmitter<{ id: string; @@ -85,125 +90,163 @@ export class Segment { this.focusableElement.focus(); } + connectedCallback() { + const childNodes = Array.from(this.hostElement.childNodes); + const nodeNames = childNodes.map(el => el.nodeName.substring(0, 10)) + const hasText = nodeNames.includes('#text'); + const hasIcon = nodeNames.includes('SCALE-ICON') + this.iconOnly = hasIcon && !hasText; + this.iconText = hasIcon && hasText; + this.handleSelectedIcon(); + } + + componentDidLoad() { + this.setChildrenIconSize(); + } + componentWillLoad() { if (this.segmentId == null) { this.segmentId = 'segment-' + i++; } } - componentDidUpdate() { - this.handleIcon(); - } - getAriaDescriptionTranslation() { - const replaceSelected = this.selected - ? this.ariaLangSelected - : this.ariaLangDeselected; - const filledText = this.ariaDescriptionTranslation - .replace(/\$position/g, `${this.position}`) - .replace(/\$selected/g, `${replaceSelected}`); - return filledText; + @Watch('selected') + handleSelectedIcon() { + if (this.iconOnly) { + Array.from(this.hostElement.childNodes).forEach((child) => { + if ( + child.nodeType === 1 && + child.nodeName.substr(0, 10) === 'SCALE-ICON' + ) { + const icon: HTMLElement = this.hostElement.querySelector( + child.nodeName + ); + this.selected + ? icon.setAttribute('selected', '') + : icon.removeAttribute('selected'); + } + }) + } } - handleIcon() { - Array.from(this.hostElement.childNodes).forEach((child) => { - if ( - child.nodeType === 1 && - child.nodeName.substr(0, 10) === 'SCALE-ICON' - ) { - const icon: HTMLElement = this.hostElement.querySelector( - child.nodeName - ); - switch (this.size) { - case 'small': - icon.setAttribute('size', '14'); - break; - case 'medium': - icon.setAttribute('size', '16'); - break; - case 'large': - icon.setAttribute('size', '20'); - break; + // getAriaDescriptionTranslation() { + // const replaceSelected = this.selected + // ? this.ariaLangSelected + // : this.ariaLangDeselected; + // const filledText = this.ariaDescriptionTranslation + // .replace(/\$position/g, `${this.position}`) + // .replace(/\$selected/g, `${replaceSelected}`); + // return filledText; + // } + + /* + * Set any children icon's size according the button size. + */ + setChildrenIconSize() { + if (this.size != null && iconSizeMap[this.size] != null) { + const icons: ScaleIcon[] = Array.from(this.hostElement.children).filter( + isScaleIcon + ); + icons.forEach((icon) => { + if (this.size == 'small') { + icon.size = iconSizeMap['small']; + } else { + icon.size = iconSizeMap['large']; } - icon.style.display = 'inline-flex'; - icon.style.marginRight = '4px'; - this.hasIcon = true; - } - if (child.nodeType === 3 && this.hostElement.childNodes.length === 1) { - this.textOnly = true; - const span = document.createElement('span'); - child.parentNode.insertBefore(span, child); - span.appendChild(child); - } - if ( - child.nodeType === 1 && - child.nodeName.substr(0, 10) === 'SCALE-ICON' && - this.hostElement.childNodes.length === 1 - ) { - this.iconOnly = true; - this.hostElement.setAttribute('icon-only', 'true'); - const icon: HTMLElement = this.hostElement.querySelector( - child.nodeName - ); - icon.style.marginRight = '0px'; - this.selected - ? icon.setAttribute('selected', '') - : icon.removeAttribute('selected'); - } - }); + }); + } } handleClick = (event: MouseEvent) => { - if (parseInt(this.selectedIndex, 10) + 1 === this.position) { - return; + if (!this.disabled) { + event.preventDefault(); + this.selected = !this.selected; + emitEvent(this, 'scaleClick', { + id: this.segmentId, + selected: this.selected, + }); } - event.preventDefault(); - this.selected = !this.selected; - emitEvent(this, 'scaleClick', { - id: this.segmentId, - selected: this.selected, - }); + }; render() { return ( {this.styles && } - +
  • + +
  • ); } @@ -216,6 +259,15 @@ export class Segment { return this.getCssOrBasePartMap('css'); } + getListItemCssClasses() { + return classNames( + "segment--list-item", + this.adjacentSiblings && + `segment--list-item--${this.adjacentSiblings.replace(/ /g, '-')}-sibling-selected`, + this.size && `segment--${this.size}`, + ); + } + getCssOrBasePartMap(mode: 'basePart' | 'css') { const prefix = mode === 'basePart' ? '' : 'segment--'; @@ -224,10 +276,9 @@ export class Segment { this.size && `${prefix}${this.size}`, this.selected && `${prefix}selected`, this.disabled && `${prefix}disabled`, - this.adjacentSiblings && - `${prefix}${this.adjacentSiblings.replace(/ /g, '-')}-sibling-selected`, - this.hasIcon && `${prefix}has-icon`, - this.iconOnly && `${prefix}icon-only` + this.iconOnly && `${prefix}icon-only`, + this.iconText && `${prefix}icon-text`, + this.multiSelect && `${prefix}multi-select` ); } } diff --git a/packages/components/src/components/segmented-button/segmented-button.css b/packages/components/src/components/segmented-button/segmented-button.css index 12720a432b..e752a70ae9 100644 --- a/packages/components/src/components/segmented-button/segmented-button.css +++ b/packages/components/src/components/segmented-button/segmented-button.css @@ -9,7 +9,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -:host { + :host { --background-color: var(--telekom-color-ui-faint); --radius: var(--telekom-radius-standard); --height: 32px; @@ -22,16 +22,23 @@ display: flex; flex-direction: column; + flex-flow:column nowrap; + + --colNum: 0; } .segmented-button { background-color: var(--background-color); border: 0; border-radius: var(--radius); - padding: 0 var(--telekom-spacing-composition-space-02); + padding: 0 var(--telekom-spacing-unit-x05); width: fit-content; height: var(--height); display: inline-grid; + grid-template-columns: repeat(var(--colNum), minmax(auto, 1fr)); + column-gap: 0; + margin-block-start: 0; + margin-block-end: 0; } .segmented-button--full-width { @@ -40,20 +47,26 @@ .segmented-button--medium { height: var(--height-medium); - padding: 0 var(--telekom-spacing-composition-space-02); + padding: 0 var(--telekom-spacing-unit-x05); } .segmented-button--large { height: var(--height-large); - padding: 0 var(--telekom-spacing-composition-space-02); + padding: 0 var(--telekom-spacing-unit-x05); } .segmented-button--label { font-size: var(--telekom-typography-font-size-body); font-weight: var(--telekom-typography-font-weight-bold); - margin-bottom: var(--telekom-spacing-composition-space-04); + margin-bottom: var(--telekom-spacing-unit-x2); } .segmented-button--helper-text { - margin-top: var(--telekom-spacing-composition-space-04); + margin-top: var(--telekom-spacing-unit-x2); } + +.segmented-button--fieldset { + border: 0; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/packages/components/src/components/segmented-button/segmented-button.tsx b/packages/components/src/components/segmented-button/segmented-button.tsx index b93fea17c1..8f6300afd4 100644 --- a/packages/components/src/components/segmented-button/segmented-button.tsx +++ b/packages/components/src/components/segmented-button/segmented-button.tsx @@ -29,10 +29,6 @@ interface SegmentStatus { selected: boolean; } -const CHECKMARK_WIDTH_SMALL = 14; -const CHECKMARK_WIDTH_MEDIUM = 18 + 12; -const CHECKMARK_WIDTH_LARGE = 20 + 18; - @Component({ tag: 'scale-segmented-button', styleUrl: 'segmented-button.css', @@ -51,8 +47,6 @@ export class SegmentedButton { @Prop() size?: 'small' | 'medium' | 'large' = 'small'; /** (optional) Allow more than one button to be selected */ @Prop() multiSelect: boolean = false; - /** (optional) the index of the selected segment */ - @Prop() selectedIndex?: number; /** (optional) If `true`, the button is disabled */ @Prop({ reflect: true }) disabled?: boolean = false; /** (optional) If `true`, expand to container width */ @@ -63,6 +57,8 @@ export class SegmentedButton { @Prop() helperText?: string = 'Please select an option'; /** (optional) Button label */ @Prop() label?: string; + /** (optional) icon only */ + @Prop() iconOnly?: boolean = false; /** (optional) Injected CSS styles */ @Prop() styles?: string; /** (optional) aria-label attribute needed for icon-only buttons */ @@ -77,6 +73,7 @@ export class SegmentedButton { container: HTMLElement; showHelperText = false; + @Listen('scaleClick') scaleClickHandler(ev: { detail: { id: string; selected: boolean } }) { let tempState: SegmentStatus[]; @@ -101,7 +98,6 @@ export class SegmentedButton { @Watch('disabled') @Watch('size') - @Watch('selectedIndex') handlePropsChange() { this.propagatePropsToChildren(); } @@ -110,49 +106,44 @@ export class SegmentedButton { * Keep props, needed in children buttons, in sync */ propagatePropsToChildren() { - this.getAllSegments().forEach((segment) => { - segment.setAttribute('size', this.size); - segment.setAttribute('selected-index', this.selectedIndex.toString()); + this.getAllSegmentedButtons().forEach((el) => { + el.setAttribute('size', this.size); if (this.disabled) { - segment.setAttribute('disabled', true && 'disabled'); + el.setAttribute('disabled', ''); + } + if (this.multiSelect) { + el.setAttribute('multi-select', ''); } }); } + connectedCallback() { + this.propagatePropsToChildren(); + } + componentDidLoad() { const tempState: SegmentStatus[] = []; - const segments = this.getAllSegments(); - this.slottedSegments = segments.length; - const longestButtonWidth = this.getLongestButtonWidth(); - segments.forEach((segment) => { + const segmentedButtons = this.getAllSegmentedButtons(); + this.slottedSegments = segmentedButtons.length; + segmentedButtons.forEach((SegmentedButton) => { this.position++; tempState.push({ - id: segment.getAttribute('segment-id') || segment.segmentId, - selected: segment.hasAttribute('selected') || segment.selected, + id: SegmentedButton.getAttribute('segment-id'), + selected: SegmentedButton.hasAttribute('selected'), }); - segment.setAttribute('position', this.position.toString()); - segment.setAttribute( + SegmentedButton.setAttribute('position', this.position.toString()); + SegmentedButton.setAttribute( 'aria-description-translation', '$position $selected' ); }); - if (!this.fullWidth) { - this.container.style.gridTemplateColumns = `repeat(${ - this.hostElement.children.length - }, ${Math.ceil(longestButtonWidth)}px)`; - } else { - this.container.style.display = 'flex'; - } - - this.selectedIndex = this.getSelectedIndex(); - this.propagatePropsToChildren(); + this.hostElement.style.setProperty('--colNum', this.slottedSegments.toString()) this.position = 0; this.status = tempState; this.setState(tempState); } componentWillUpdate() { - this.selectedIndex = this.getSelectedIndex(); this.showHelperText = false; if ( this.invalid && @@ -162,19 +153,6 @@ export class SegmentedButton { } } - getSelectedIndex() { - if (this.multiSelect) { - // in multi-select having no selected segments is allowed - return -1; - } else { - const allSegments = this.getAllSegments(); - const selectedIndex = allSegments.findIndex( - (el: HTMLScaleSegmentElement) => el.selected === true - ); - return selectedIndex; - } - } - getAdjacentSiblings = (tempState, i) => { let adjacentSiblings = ''; if (i !== 0 && tempState[i].selected && tempState[i - 1].selected) { @@ -192,43 +170,16 @@ export class SegmentedButton { return adjacentSiblings; }; - // all segmented buttons should have the same width, based on the largest one - getLongestButtonWidth() { - let tempWidth = 0; - Array.from(this.hostElement.children).forEach((child) => { - const selected = child.hasAttribute('selected'); - const iconOnly = child.hasAttribute('icon-only'); - const checkmark = - this.size === 'small' - ? CHECKMARK_WIDTH_SMALL - : this.size === 'medium' - ? CHECKMARK_WIDTH_MEDIUM - : CHECKMARK_WIDTH_LARGE; - if (selected || iconOnly) { - tempWidth = - child.getBoundingClientRect().width > tempWidth - ? child.getBoundingClientRect().width - : tempWidth; - } else { - tempWidth = - child.getBoundingClientRect().width + checkmark > tempWidth - ? child.getBoundingClientRect().width + checkmark - : tempWidth; - } - }); - return tempWidth; - } - setState(tempState: SegmentStatus[]) { - const segments = Array.from( + const segmentedButtons = Array.from( this.hostElement.querySelectorAll('scale-segment') ); - segments.forEach((segment, i) => { - segment.setAttribute( + segmentedButtons.forEach((segmentedButton, i) => { + segmentedButton.setAttribute( 'adjacent-siblings', this.getAdjacentSiblings(tempState, i) ); - segment.setAttribute( + segmentedButton.setAttribute( 'selected', tempState[i].selected ? 'true' : 'false' ); @@ -237,7 +188,7 @@ export class SegmentedButton { emitEvent(this, 'scaleChange', this.status); } - getAllSegments() { + getAllSegmentedButtons() { return Array.from(this.hostElement.querySelectorAll('scale-segment')); } @@ -253,25 +204,31 @@ export class SegmentedButton { return ( {this.styles && } - {this.label && ( - {this.label} - )} -
    (this.container = el as HTMLInputElement)} - > - -
    - {this.showHelperText && ( - - )} +
    + {this.label && ( + {this.label} + )} +
      (this.container = el as HTMLElement)} + aria-description={ + this.showHelperText && this.helperText ? this.helperText : null + } + > + +
    + {this.showHelperText && ( + + )} +
    +
    ); } diff --git a/packages/storybook-vue/stories/components/segmented-button/SegmentedButton.stories.mdx b/packages/storybook-vue/stories/components/segmented-button/SegmentedButton.stories.mdx index 6e52838578..08cff219e2 100644 --- a/packages/storybook-vue/stories/components/segmented-button/SegmentedButton.stories.mdx +++ b/packages/storybook-vue/stories/components/segmented-button/SegmentedButton.stories.mdx @@ -26,13 +26,12 @@ export const Template = (args, { argTypes }) => ({ :styles="styles" :invalid="invalid" :helper-text="helperText" - :full-width="fullWidth" :label="label" > - Apple - One+ - Samsung - Huawei + Apple + One+ + Samsung + Huawei `, }); @@ -187,6 +186,7 @@ export const InvalidTemplate = (args, { argTypes }) => ({ Beta Component + ## Standard @@ -206,51 +206,6 @@ export const InvalidTemplate = (args, { argTypes }) => ({ ``` -### Scoped CSS variables - -```css -:host { - --background-color: var(--telekom-color-ui-faint); - --radius: var(--telekom-radius-standard); - --height: 32px; - - /* medium styles */ - --height-medium: 40px; - - /* large styles */ - --height-large: 44px; -} - -:host(scale-segmented) { - /* not selected */ - --background-color-selected: var(--telekom-color-ui-base); - --button-radius: 6px; - --padding-top-bottom: var(--telekom-spacing-composition-space-03); - --height: 28px; - --font: var(--telekom-text-style-caption-bold); - --label-disabled: var(--telekom-color-text-and-icon-disabled); - --transition: all var(--telekom-motion-duration-transition) - var(--telekom-motion-easing-standard); - --color: var(--telekom-color-text-and-icon-standard); - - --color-selected: var(--telekom-color-text-and-icon-primary-standard); - - /* medium styles */ - --font-medium: var(--telekom-text-style-ui-bold); - --height-medium: 36px; - - /* large styles */ - --font-large: var(--telekom-text-style-ui-bold); - --height-large: 40px; - - /* hover styles */ - --background-color-hover: var(--telekom-color-ui-state-fill-hovered); - - /* active styles */ - --background-color-active: var(--telekom-color-ui-state-fill-pressed); -} -``` - ## Multi Select @@ -260,10 +215,10 @@ export const InvalidTemplate = (args, { argTypes }) => ({ ```html - - Apple - One+ - Samsung + + Apple + One+ + Samsung Huawei ``` @@ -314,19 +269,26 @@ export const InvalidTemplate = (args, { argTypes }) => ({ ```html - - - - - - - - - - - - - + + + + ``` @@ -340,26 +302,30 @@ export const InvalidTemplate = (args, { argTypes }) => ({ ```html - - - - Apple - - - - - One+ - - - - - Samsung - - - - - Huawei - + Apple + One+ + Samsung + Huawei ``` @@ -378,25 +344,29 @@ export const InvalidTemplate = (args, { argTypes }) => ({ ```html - - - - Apple - - - - - One+ - - - - - Samsung - - - - - Huawei - + Apple + One+ + Samsung + Huawei ``` From 01788c3996477ad230df7a74b0d1cef1c28e5c4c Mon Sep 17 00:00:00 2001 From: felixw Date: Thu, 4 Jan 2024 16:00:29 +0100 Subject: [PATCH 2/7] fix: shadows and clipping in multi select --- .../src/components/segment/segment.css | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/components/src/components/segment/segment.css b/packages/components/src/components/segment/segment.css index 40133fa1e9..28dba28a61 100644 --- a/packages/components/src/components/segment/segment.css +++ b/packages/components/src/components/segment/segment.css @@ -110,11 +110,6 @@ padding-right: 12px; } -/* .segment--multi-select.segment:not(.segment--icon-only) .segment--mask { - padding-left: 19px; - padding-right: 19px; -} */ - .segment--small { height: var(--height); } @@ -175,28 +170,36 @@ /* shadows left and right */ .segment--list-item--left-sibling-selected { - border-radius: 0 var(--button-radius) var(--button-radius) 0; margin-left: 0; clip-path: inset(-2.75rem -2.75rem -2.75rem 0); - backface-visibility: hidden; - overflow: hidden; +} + +.segment--list-item--left-sibling-selected.segment--list-item, +.segment--list-item--left-sibling-selected .segment { + border-radius: 0 var(--button-radius) var(--button-radius) 0; } .segment--list-item--right-sibling-selected { - border-radius: var(--button-radius) 0 0 var(--button-radius); clip-path: inset(-2.75rem 0 -2.75rem -2.75rem); margin-right: 0; - backface-visibility: hidden; - overflow: hidden; position: relative; } +.segment--list-item--right-sibling-selected.segment--list-item, +.segment--list-item--right-sibling-selected .segment + { + border-radius: var(--button-radius) 0 0 var(--button-radius); +} + + .segment--list-item--left-right-sibling-selected { clip-path: inset(-2.75rem 0 -2.75rem 0); - border-radius: 0; margin-left: 0; margin-right: 0; - backface-visibility: hidden; +} + +.segment--list-item--left-right-sibling-selected .segment { + border-radius: 0; } .segment:not(.segment--disabled):hover .segment--mask { From b7930573f751a7e5a5bfc6943616d3ddb55613ba Mon Sep 17 00:00:00 2001 From: felixw Date: Thu, 4 Jan 2024 16:00:50 +0100 Subject: [PATCH 3/7] docs: add sample --- .../components/src/html/segmented-button.html | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 packages/components/src/html/segmented-button.html diff --git a/packages/components/src/html/segmented-button.html b/packages/components/src/html/segmented-button.html new file mode 100644 index 0000000000..b52a4bbd8d --- /dev/null +++ b/packages/components/src/html/segmented-button.html @@ -0,0 +1,93 @@ + + + + + + Stencil Component Starter + + + + + + + +

    Segmented Button

    + + + +

    Standard

    + + + Apple + One+ + Samsung + Huawei + + +

    Multi select

    + + + Apple + One+ + Samsung + Huawei + + +

    Icon Only

    + + + + + + + + + + + + + + + + + + +

    Icon Text

    + + + + + + Apple + + + + + One+ + + + + + Samsung + + + + + Huawei + + + + From 4fb3b0a1101315d2bb0b073e9cfaf7fc668ffe7c Mon Sep 17 00:00:00 2001 From: felixw Date: Fri, 5 Jan 2024 12:09:24 +0100 Subject: [PATCH 4/7] fix: wip shadow clip --- packages/components/src/components/segment/segment.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/components/src/components/segment/segment.css b/packages/components/src/components/segment/segment.css index 28dba28a61..7b48945177 100644 --- a/packages/components/src/components/segment/segment.css +++ b/packages/components/src/components/segment/segment.css @@ -43,6 +43,10 @@ align-items: center; justify-content: center; flex: 1; + + /* TODO: remove this, used for debugging shadow and clip-path */ + /* --telekom-shadow-raised-standard: 0px 8px 32px 0px red, 0px 4px 8px 0px red; */ + } .segment { From a85870dc5b07a718c1cd83c0fd417af666bc04bd Mon Sep 17 00:00:00 2001 From: felixw Date: Fri, 5 Jan 2024 16:33:34 +0100 Subject: [PATCH 5/7] fix: handle icononly and texticon segments correnctly --- .../src/components/segment/segment.css | 47 ++++++++++--------- .../src/components/segment/segment.tsx | 7 ++- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/packages/components/src/components/segment/segment.css b/packages/components/src/components/segment/segment.css index 7b48945177..da8b1b954f 100644 --- a/packages/components/src/components/segment/segment.css +++ b/packages/components/src/components/segment/segment.css @@ -92,23 +92,6 @@ color: var(--color); } -.segment--icon-only.segment--small .segment--mask { - /* padding-left: var(--telekom-spacing-composition-space-05); - padding-right: var(--telekom-spacing-composition-space-05); */ - padding-left: 0.625rem; - padding-right: 0.625rem; -} - -.segment--icon-only.segment--medium .segment--mask { - padding-left: var(--telekom-spacing-composition-space-06); - padding-right: var(--telekom-spacing-composition-space-06); -} - -.segment--icon-only.segment--large .segment--mask { - padding-left: var(--telekom-spacing-composition-space-07); - padding-right: var(--telekom-spacing-composition-space-07); -} - .segment--small .segment--mask { padding-left: 8px; padding-right: 12px; @@ -143,12 +126,6 @@ padding-right: var(--telekom-spacing-composition-space-07); } - -.segment--icon-only .segment--mask { - padding-left: var(--telekom-spacing-composition-space-05); - padding-right: var(--telekom-spacing-composition-space-05); -} - /* selected */ .segment--selected { @@ -321,4 +298,28 @@ .segment--list-item--left-sibling-selected { +} + + /* icon only */ + + .segment--icon-only.segment--small .segment--mask { + /* padding-left: var(--telekom-spacing-composition-space-05); + padding-right: var(--telekom-spacing-composition-space-05); */ + padding-left: 0.625rem; + padding-right: 0.625rem; +} + +.segment--icon-only.segment--medium .segment--mask { + padding-left: var(--telekom-spacing-composition-space-06); + padding-right: var(--telekom-spacing-composition-space-06); +} + +.segment--icon-only.segment--large .segment--mask { + padding-left: var(--telekom-spacing-composition-space-07); + padding-right: var(--telekom-spacing-composition-space-07); +} + +.segment--icon-only .segment--mask { + padding-left: var(--telekom-spacing-composition-space-05); + padding-right: var(--telekom-spacing-composition-space-05); } \ No newline at end of file diff --git a/packages/components/src/components/segment/segment.tsx b/packages/components/src/components/segment/segment.tsx index e6e9761923..66672f8cb7 100644 --- a/packages/components/src/components/segment/segment.tsx +++ b/packages/components/src/components/segment/segment.tsx @@ -92,7 +92,12 @@ export class Segment { connectedCallback() { const childNodes = Array.from(this.hostElement.childNodes); - const nodeNames = childNodes.map(el => el.nodeName.substring(0, 10)) + let nodeNames = [] + for (let el of childNodes) { + if ( (el.nodeValue && el.nodeValue.replace('\n', '\\n').trim() !== '\\n') || el.nodeType !== 3 ) { + nodeNames.push(el.nodeName.substring(0, 10)) + } + } const hasText = nodeNames.includes('#text'); const hasIcon = nodeNames.includes('SCALE-ICON') this.iconOnly = hasIcon && !hasText; From ff396e690b6fe9e19944603fbcaacc4a1c506dec Mon Sep 17 00:00:00 2001 From: felixw Date: Wed, 17 Jan 2024 12:43:18 +0100 Subject: [PATCH 6/7] fix: refactor wip --- .../segmented-button/segmented-button.tsx | 70 ++++++++----------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/packages/components/src/components/segmented-button/segmented-button.tsx b/packages/components/src/components/segmented-button/segmented-button.tsx index 8f6300afd4..e9aa2ca5a6 100644 --- a/packages/components/src/components/segmented-button/segmented-button.tsx +++ b/packages/components/src/components/segmented-button/segmented-button.tsx @@ -107,40 +107,36 @@ export class SegmentedButton { */ propagatePropsToChildren() { this.getAllSegmentedButtons().forEach((el) => { - el.setAttribute('size', this.size); if (this.disabled) { - el.setAttribute('disabled', ''); - } - if (this.multiSelect) { - el.setAttribute('multi-select', ''); + el.setAttribute('disabled', true && 'disabled'); } + if (this.iconOnly) { + el.setAttribute('icon-only', 'true'); + } }); } - connectedCallback() { - this.propagatePropsToChildren(); - } - componentDidLoad() { const tempState: SegmentStatus[] = []; const segmentedButtons = this.getAllSegmentedButtons(); this.slottedSegments = segmentedButtons.length; - segmentedButtons.forEach((SegmentedButton) => { + segmentedButtons.forEach((segment) => { this.position++; tempState.push({ - id: SegmentedButton.getAttribute('segment-id'), - selected: SegmentedButton.hasAttribute('selected'), + id: segment.getAttribute('segment-id'), + selected: segment.hasAttribute('selected') && segment.getAttribute('selected') !== 'false' }); - SegmentedButton.setAttribute('position', this.position.toString()); - SegmentedButton.setAttribute( + segment.setAttribute('position', this.position.toString()); + segment.setAttribute( 'aria-description-translation', '$position $selected' ); }); this.hostElement.style.setProperty('--colNum', this.slottedSegments.toString()) + this.propagatePropsToChildren(); this.position = 0; this.status = tempState; - this.setState(tempState); + } componentWillUpdate() { @@ -204,31 +200,25 @@ export class SegmentedButton { return ( {this.styles && } -
    - {this.label && ( - {this.label} - )} -
      (this.container = el as HTMLElement)} - aria-description={ - this.showHelperText && this.helperText ? this.helperText : null - } - > - -
    - {this.showHelperText && ( - - )} -
    - + {this.label && ( + {this.label} + )} +
    (this.container = el as HTMLInputElement)} + > + +
    + {this.showHelperText && ( + + )}
    ); } From 96f0982f447ed10305a78615c220286a0dc36573 Mon Sep 17 00:00:00 2001 From: felixw Date: Thu, 25 Jan 2024 14:26:57 +0100 Subject: [PATCH 7/7] fix: pass segment value in change event --- packages/components/src/components/segment/segment.tsx | 7 ++++++- .../src/components/segmented-button/segmented-button.tsx | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/components/src/components/segment/segment.tsx b/packages/components/src/components/segment/segment.tsx index 66672f8cb7..1d59e327c7 100644 --- a/packages/components/src/components/segment/segment.tsx +++ b/packages/components/src/components/segment/segment.tsx @@ -76,14 +76,17 @@ export class Segment { @Event({ eventName: 'scale-click' }) scaleClick!: EventEmitter<{ id: string; selected: boolean; + value: string; }>; /** @deprecated in v3 in favor of kebab-case event names */ @Event({ eventName: 'scaleClick' }) scaleClickLegacy!: EventEmitter<{ id: string; selected: boolean; + value: string; }>; private focusableElement: HTMLElement; + private value: string; @Method() async setFocus() { @@ -101,8 +104,9 @@ export class Segment { const hasText = nodeNames.includes('#text'); const hasIcon = nodeNames.includes('SCALE-ICON') this.iconOnly = hasIcon && !hasText; - this.iconText = hasIcon && hasText; + this.iconText = hasIcon && hasText; this.handleSelectedIcon(); + this.value = this.hostElement.textContent.trim(); } componentDidLoad() { @@ -169,6 +173,7 @@ export class Segment { emitEvent(this, 'scaleClick', { id: this.segmentId, selected: this.selected, + value: this.value, }); } diff --git a/packages/components/src/components/segmented-button/segmented-button.tsx b/packages/components/src/components/segmented-button/segmented-button.tsx index e9aa2ca5a6..f5a0773f55 100644 --- a/packages/components/src/components/segmented-button/segmented-button.tsx +++ b/packages/components/src/components/segmented-button/segmented-button.tsx @@ -27,6 +27,7 @@ import { emitEvent } from '../../utils/utils'; interface SegmentStatus { id: string; selected: boolean; + value?: string; } @Component({ @@ -136,7 +137,6 @@ export class SegmentedButton { this.propagatePropsToChildren(); this.position = 0; this.status = tempState; - } componentWillUpdate() {