Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preview chips segmentedbutton #1495

Merged
merged 91 commits into from
Jan 25, 2023
Merged
Changes from 1 commit
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
938f1cd
feat(chip): init component
marvinLaubenstein Nov 10, 2022
b698696
refactor: cleaning up component
marvinLaubenstein Nov 14, 2022
4772c49
feat: init new chip concept
marvinLaubenstein Nov 14, 2022
fcf9f7a
feat: adding dismissible and selected functionality's
marvinLaubenstein Nov 14, 2022
3ca6b13
feat: adding possibility for icons
marvinLaubenstein Nov 17, 2022
d39c6f0
feat: adding unit test and storybook init
marvinLaubenstein Nov 17, 2022
138df19
feat: adding accessibility, disabled and removing magic numbers
marvinLaubenstein Nov 18, 2022
ab534f7
refactor: solving linting errors
marvinLaubenstein Nov 18, 2022
8a891c2
fix: naming
marvinLaubenstein Nov 18, 2022
bce2ae3
fix: reference error
marvinLaubenstein Nov 21, 2022
3ac9abc
fix: reference error
marvinLaubenstein Nov 21, 2022
4410ce2
fix: reference error
marvinLaubenstein Nov 21, 2022
0d5d518
refactor: changing story naming
marvinLaubenstein Nov 21, 2022
7588c67
feat: init visual tests
marvinLaubenstein Nov 21, 2022
6b28ba7
refactor: deleting href test case
marvinLaubenstein Nov 22, 2022
a57fef1
refactor: changing close trigger
marvinLaubenstein Nov 22, 2022
8f6035c
refactor: changing focus outline style
marvinLaubenstein Nov 22, 2022
8834668
refactor: updating visual snapshots
marvinLaubenstein Nov 22, 2022
10a085b
refactor: deleting unneeded props
marvinLaubenstein Dec 5, 2022
971978f
feat: adding focus and active state
marvinLaubenstein Dec 5, 2022
0e0439e
feat: adding storybook stories
marvinLaubenstein Dec 7, 2022
fc30039
fix: not loading project
marvinLaubenstein Dec 12, 2022
ef36ef7
feat: adding focus state
marvinLaubenstein Dec 12, 2022
53c465c
feat: implement new design
marvinLaubenstein Dec 13, 2022
28e93c4
refactor: storybook stories
marvinLaubenstein Dec 15, 2022
1cf8083
feat: add segmented-button
felix-ico Dec 16, 2022
7c88770
refactor: changing stories chip label and deleting icon size property
marvinLaubenstein Dec 20, 2022
cada6f2
Merge branch 'main' into feat/chip
marvinLaubenstein Dec 20, 2022
54993ac
refactor: changing stories label
marvinLaubenstein Dec 21, 2022
db005e0
fix: removing unneeded icon size property
marvinLaubenstein Dec 21, 2022
aacc01e
refactor: updating visual snapshots
marvinLaubenstein Dec 21, 2022
e4b7d6b
test(visual): update snapshots (#1434)
github-actions[bot] Dec 21, 2022
988f676
fix: solving color problem and transition bug
marvinLaubenstein Dec 22, 2022
928e842
refactor: changing label prop to slot
marvinLaubenstein Dec 22, 2022
9813dde
test(visual): update snapshots (#1436)
github-actions[bot] Dec 22, 2022
2068692
refactor: changing icon to selected
marvinLaubenstein Dec 22, 2022
d4bc836
fix: add refactoring from separate branch
felix-ico Jan 9, 2023
b3a330d
test(visual): update snapshots (#1456)
github-actions[bot] Jan 9, 2023
2262c57
refactor: renaming segmented-button to segment and segmented-button-g…
marvinLaubenstein Jan 9, 2023
d11dc55
test(visual): update snapshots (#1457)
github-actions[bot] Jan 9, 2023
f8abb5a
test(visual): update snapshots (#1459)
github-actions[bot] Jan 10, 2023
54157ef
test(visual): update snapshots (#1461)
github-actions[bot] Jan 10, 2023
4166608
test(visual): update snapshots (#1462)
github-actions[bot] Jan 10, 2023
d1731a3
feat: disable deselecting on single-select
felix-ico Jan 11, 2023
2bd60d3
fix: lint
felix-ico Jan 11, 2023
0e4c206
fix: format
felix-ico Jan 11, 2023
d771091
fix: improve naming
felix-ico Jan 11, 2023
952a8f6
test: update snapshot
felix-ico Jan 11, 2023
029f2e2
refactor: introducing new dynamic and persistent structure
marvinLaubenstein Jan 11, 2023
5ef905d
refactor: changing disabled functionality
marvinLaubenstein Jan 12, 2023
054ee4b
refactor: updating storybook stories
marvinLaubenstein Jan 12, 2023
bcc2b09
fix: solving loading issues
marvinLaubenstein Jan 12, 2023
f4dab4a
refactor: updating visual snapshots
marvinLaubenstein Jan 12, 2023
91fb881
feat: adding example story for dynamic
marvinLaubenstein Jan 13, 2023
6ba23f6
refactor: changing selected icon state setting
marvinLaubenstein Jan 13, 2023
d60fa45
refactor: improving storybook page
marvinLaubenstein Jan 13, 2023
8fd306e
refactor: updating visual snapshots
marvinLaubenstein Jan 13, 2023
543eac0
Merge branch 'main' into feat/chip
marvinLaubenstein Jan 13, 2023
e5e50e8
fix: merging problem
marvinLaubenstein Jan 13, 2023
818db5d
fix: solving storybook preview problem
marvinLaubenstein Jan 13, 2023
e95c300
fix: quickfix
marvinLaubenstein Jan 13, 2023
2beaef5
refactor: removing example story
marvinLaubenstein Jan 16, 2023
75bbcdd
refactor: changing minor styling in storybook
marvinLaubenstein Jan 16, 2023
3cea555
feat: add fullwidth prop
felix-ico Jan 18, 2023
9bcd820
fix: remove ts-ignore
felix-ico Jan 18, 2023
5fb26fb
feat: add fullwidth prop to story
felix-ico Jan 18, 2023
e7ce143
fix: format
felix-ico Jan 18, 2023
5af69c8
fix: missing closing bracket
felix-ico Jan 18, 2023
a4fca19
fix: style
felix-ico Jan 18, 2023
cc826e3
fix: format
felix-ico Jan 18, 2023
76c6ddf
Merge branch 'feat/chip' of github.com:telekom/scale into preview-chi…
felix-ico Jan 20, 2023
14a4757
Merge branch 'feat/segmented-button' of github.com:telekom/scale into…
felix-ico Jan 20, 2023
4545765
test(visual): update snapshots (#1496)
github-actions[bot] Jan 20, 2023
81f9480
Feat/chip and segmented button usage text (#1501)
marvinLaubenstein Jan 23, 2023
6a790a5
refactor: updating segmented button snapshots
marvinLaubenstein Jan 23, 2023
276e938
refactor: changing design matters
marvinLaubenstein Jan 23, 2023
9dab899
fix: solving design notes
marvinLaubenstein Jan 24, 2023
2371fef
refactor: deleting slitted view / mobile view issues
marvinLaubenstein Jan 24, 2023
0d8d5b0
refactor: including design feedback
marvinLaubenstein Jan 24, 2023
a95f917
Refactor/chips segmentedbutton (#1512)
marvinLaubenstein Jan 25, 2023
5c454ee
feat: add segmented-button (#1429)
felix-ico Jan 25, 2023
44cf9f2
test(visual): update snapshots (#1497)
github-actions[bot] Jan 25, 2023
779db35
test(visual): update snapshots (#1513)
github-actions[bot] Jan 25, 2023
b46ebe0
test(visual): update snapshots (#1514)
github-actions[bot] Jan 25, 2023
3c5e393
fix: solving visual testing fails
marvinLaubenstein Jan 25, 2023
980ee6b
fix: remove unneeded dep, remove unneeded file
felix-ico Jan 25, 2023
350d666
Merge branch 'preview-chips-segmentedbutton' of github.com:telekom/sc…
felix-ico Jan 25, 2023
35f42bb
fix: remove wrong deprecation warning
felix-ico Jan 25, 2023
34cc22e
test: disable segmented button vis test
felix-ico Jan 25, 2023
6cacb81
test(segmented-button): disable
nowseemee Jan 25, 2023
c926271
test(visual): update snapshots (#1516)
github-actions[bot] Jan 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add segmented-button
felix-ico committed Dec 16, 2022
commit 1cf808372825ee3c054285de279448e974fda616
2 changes: 2 additions & 0 deletions packages/components/src/components/helper-text/readme.md
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@

- [scale-dropdown](../dropdown)
- [scale-dropdown-select](../dropdown-select)
- [scale-segmented-button-group](../segmented-button-group)
- [scale-text-field](../text-field)

### Depends on
@@ -54,6 +55,7 @@ graph TD;
scale-helper-text --> scale-icon-alert-success
scale-dropdown --> scale-helper-text
scale-dropdown-select --> scale-helper-text
scale-segmented-button-group --> scale-helper-text
scale-text-field --> scale-helper-text
style scale-helper-text fill:#f9f,stroke:#333,stroke-width:4px
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# scale-segmented-button-group



<!-- Auto Generated Below -->


## Properties

| Property | Attribute | Description | Type | Default |
| ---------------------- | ------------------------ | ------------------------------------------------------------------ | ---------------------------- | ----------------------------------------------------- |
| `ariaLabelTranslation` | `aria-label-translation` | (optional) aria-label attribute needed for icon-only buttons | `string` | ``segment button group with $slottedButtons buttons`` |
| `disabled` | `disabled` | (optional) If `true`, the group is disabled | `boolean` | `false` |
| `fullWidth` | `full-width` | (optional) If `true`, expand to container width | `boolean` | `false` |
| `helperText` | `helper-text` | (optional) If `true`, show error message if no element is selected | `string` | `"Please select an option"` |
| `label` | `label` | (optional) Group label | `string` | `undefined` |
| `longestButtonWidth` | `longest-button-width` | | `string` | `undefined` |
| `multiSelect` | `multi-select` | (optional) Allow more than one button to be selected | `boolean` | `false` |
| `required` | `required` | (optional) If `true`, show error message if no element is selected | `boolean` | `false` |
| `size` | `size` | (optional) The size of the button | `"large" \| "small" \| "xl"` | `'small'` |
| `styles` | `styles` | (optional) Injected CSS styles | `string` | `undefined` |


## Events

| Event | Description | Type |
| -------------- | -------------------------------------------------------------------------------------------------- | ------------------ |
| `scale-change` | Emitted when button is clicked | `CustomEvent<any>` |
| `scaleChange` | <span style="color:red">**[DEPRECATED]**</span> in v3 in favor of kebab-case event names<br/><br/> | `CustomEvent<any>` |


## Dependencies

### Depends on

- [scale-helper-text](../helper-text)

### Graph
```mermaid
graph TD;
scale-segmented-button-group --> scale-helper-text
scale-helper-text --> scale-icon-alert-information
scale-helper-text --> scale-icon-alert-error
scale-helper-text --> scale-icon-alert-success
style scale-segmented-button-group fill:#f9f,stroke:#333,stroke-width:4px
```

----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* @license
* Scale https://github.com/telekom/scale
*
* Copyright (c) 2021 Egor Kirpichev and contributors, Deutsche Telekom AG
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

:host {
--background-color: var(--telekom-color-ui-faint);
--radius: var(--telekom-radius-standard);
--height: 36px;
display: flex;
flex-direction: column;
}

.segmented-button-group {
background-color: var(--background-color);
border: 0;
border-radius: var(--radius);
padding: 2px;
width: fit-content;
height: 32px;
display: inline-grid;
}

.segmented-button-group--full-width {
width: 100%;
}

.segmented-button-group--large {
height: 40px;
--height: 40px;
padding: 0 2px 0 2px;
}

.segmented-button-group--xl {
height: 44px;
--height: 44px;
padding: 0 2px 0 2px;
}

.segmented-button-group--label {
font-size: var(--telekom-typography-font-size-body);
font-weight: var(--telekom-typography-font-weight-bold);
margin-bottom: var(--telekom-spacing-unit-x2)
}

.segmented-button-group--helper-text {
margin-top: var(--telekom-spacing-unit-x2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/**
* @license
* Scale https://github.com/telekom/scale
*
* Copyright (c) 2021 Egor Kirpichev and contributors, Deutsche Telekom AG
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import {
Component,
Prop,
h,
Host,
Element,
State,
Listen,
Event,
EventEmitter,
Watch,
} from '@stencil/core';
import classNames from 'classnames';
import { emitEvent } from '../../utils/utils';

interface ButtonStatus {
id: string;
selected: boolean;
}

const CHECKMARK_WIDTH_SMALL = 14;
const CHECKMARK_WIDTH_LARGE = 18;

@Component({
tag: 'scale-segmented-button-group',
styleUrl: 'segmented-button-group.css',
shadow: true,
})
export class SegmentedButtonGroup {
/** segmented button position within group */
position = 0;

slottedButtons = 0;

@Element() hostElement: HTMLElement;
/** state */
@State() status: ButtonStatus[] = [];
/** (optional) The size of the button */
@Prop() size?: 'small' | 'large' | 'xl' = 'small';
/** (optional) Allow more than one button to be selected */
@Prop() multiSelect: boolean = false;
/** (optional) If `true`, the group is disabled */
@Prop({reflect: true}) disabled?: boolean = false;
/** (optional) If `true`, expand to container width */
@Prop() fullWidth?: boolean = false;
/** (optional) If `true`, show error message if no element is selected */
@Prop() required?: boolean = false;
/** (optional) If `true`, show error message if no element is selected */
@Prop() helperText?: string = "Please select an option";
/** (optional) Group label */
@Prop() label?: string;
/** (optional) Injected CSS styles */
@Prop() styles?: string;
/** (optional) aria-label attribute needed for icon-only buttons */
@Prop()
ariaLabelTranslation = `segment button group with $slottedButtons buttons`;
@Prop({ mutable: true })
longestButtonWidth: string;
/** Emitted when button is clicked */
@Event({ eventName: 'scale-change' }) scaleChange: EventEmitter;
/** @deprecated in v3 in favor of kebab-case event names */
@Event({ eventName: 'scaleChange' }) scaleChangeLegacy: EventEmitter;


container: HTMLElement;
showHelperText = false;
@Listen('scaleClick')
scaleClickHandler(ev: { detail: { id: string; selected: boolean } }) {
let tempState: ButtonStatus[];
if (!this.multiSelect) {
if (!ev.detail.selected) {
tempState = this.status.map((obj) =>
ev.detail.id === obj.id ? ev.detail : { ...obj }
);
/* clicked button has now selected state */
} else {
tempState = this.status.map((obj) =>
ev.detail.id === obj.id ? ev.detail : { ...obj, selected: false }
);
}
} else {
tempState = this.status.map((obj) =>
ev.detail.id === obj.id ? ev.detail : { ...obj }
);
}
this.setState(tempState);
}

@Watch('disabled')
@Watch('size')
handlePropsChange() {
this.propagatePropsToChildren();
}

/**
* Keep props, needed in children buttons, in sync
*/
propagatePropsToChildren() {
this.getAllSegmentedButtons().forEach((el) => {
el.setAttribute('size', this.size);
el.setAttribute('disabled', this.disabled && 'disabled');
});
}

componentDidLoad() {
const tempState: ButtonStatus[] = [];
const segmentedButtons = this.getAllSegmentedButtons();
this.slottedButtons = segmentedButtons.length;
const longestButtonWidth = this.getLongestButtonWidth();
segmentedButtons.forEach((SegmentedButton) => {
this.position++;
tempState.push({
id: SegmentedButton.getAttribute('segmented-button-id'),
selected: SegmentedButton.hasAttribute('selected'),
});
SegmentedButton.setAttribute('position', this.position.toString());
SegmentedButton.setAttribute(
'aria-description-translation',
'$position $selected'
);
});
// @ts-ignore
// this.container.style = `grid-template-columns: ${`minmax(0, ${Math.ceil(longestButtonWidth)}px) `.repeat(this.hostElement.children.length)};`;
this.container.style = `grid-template-columns: repeat(${this.hostElement.children.length}, ${Math.ceil(longestButtonWidth)}px);`;

this.propagatePropsToChildren();
this.position = 0;
this.status = tempState;
this.setState(tempState);
}

componentWillUpdate() {
this.showHelperText = false;
if (this.required && this.status.filter(e => e.selected === true).length <= 0) {
this.showHelperText = true;
}
}

getAdjacentSiblings = (tempState, i) => {
let adjacentSiblings = '';
if (i !== 0 && tempState[i].selected && tempState[i - 1].selected) {
adjacentSiblings = 'left';
}
if (
i !== tempState.length - 1 &&
tempState[i].selected &&
tempState[i + 1].selected
) {
adjacentSiblings = `${
adjacentSiblings ? adjacentSiblings + ' right' : 'right'
}`;
}
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 : 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: ButtonStatus[]) {
const segmentedButtons = Array.from(
this.hostElement.querySelectorAll('scale-segmented-button')
);
segmentedButtons.forEach((segmentedButton, i) => {
segmentedButton.setAttribute(
'adjacent-siblings',
this.getAdjacentSiblings(tempState, i)
);
segmentedButton.setAttribute(
'selected',
tempState[i].selected ? 'true' : 'false'
);
});
this.status = tempState;
emitEvent(this, 'scaleChange', this.status);
}

getAllSegmentedButtons() {
return Array.from(
this.hostElement.querySelectorAll('scale-segmented-button')
);
}

getAriaLabelTranslation() {
const filledText = this.ariaLabelTranslation.replace(
/\$slottedButtons/g,
`${this.slottedButtons}`
);
return filledText;
}

render() {
return (
<Host>
{this.styles && <style>{this.styles}</style>}
{this.label &&
<span class="segmented-button-group--label"> {this.label} </span>
}
<div
class={this.getCssClassMap()}
part={this.getBasePartMap()}
aria-label={this.getAriaLabelTranslation()}
role="group"
ref={(el) => this.container = el as HTMLInputElement}
>
<slot />

</div>
{ this.showHelperText &&
<scale-helper-text
class="segmented-button-group--helper-text"
helperText={this.helperText}
variant={'danger'}
></scale-helper-text>}
</Host>
);
}

getBasePartMap() {
return this.getCssOrBasePartMap('basePart');
}

getCssClassMap() {
return this.getCssOrBasePartMap('css');
}

getCssOrBasePartMap(mode: 'basePart' | 'css') {
const prefix = mode === 'basePart' ? '' : 'segmented-button-group--';

return classNames(
'segmented-button-group',
this.size && `${prefix}${this.size}`,
this.fullWidth && `${prefix}full-width`
);
}
}
65 changes: 65 additions & 0 deletions packages/components/src/components/segmented-button/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# scale-segmented-button



<!-- Auto Generated Below -->


## Properties

| Property | Attribute | Description | Type | Default |
| ---------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | -------------- |
| `adjacentSiblings` | `adjacent-siblings` | | `"left" \| "leftright" \| "right"` | `undefined` |
| `ariaDescriptionTranslation` | `aria-description-translation` | a11y text for getting meaningful value. `$buttonNumber` and `$selected` are template variables and will be replaces by their corresponding properties. | `string` | `'$selected'` |
| `ariaLabelSegmentedButton` | `aria-label-segmented-button` | (optional) aria-label attribute needed for icon-only buttons | `string` | `undefined` |
| `ariaLangDeselected` | `aria-lang-deselected` | (optional) translation of 'deselected | `string` | `'deselected'` |
| `ariaLangSelected` | `aria-lang-selected` | (optional) translation of 'selected | `string` | `'selected'` |
| `disabled` | `disabled` | (optional) If `true`, the button is disabled | `boolean` | `false` |
| `hasIcon` | `has-icon` | (optional) position within group | `boolean` | `undefined` |
| `iconOnly` | `icon-only` | (optional) position within group | `boolean` | `undefined` |
| `position` | `position` | (optional) position within group | `number` | `undefined` |
| `segmentedButtonId` | `segmented-button-id` | (optional) button's id | `string` | `undefined` |
| `selected` | `selected` | (optional) If `true`, the button is selected | `boolean` | `false` |
| `size` | `size` | (optional) The size of the button | `"large" \| "small" \| "xl"` | `'small'` |
| `styles` | `styles` | (optional) Injected CSS styles | `string` | `undefined` |
| `textOnly` | `text-only` | (optional) position within group | `boolean` | `undefined` |
| `width` | `width` | (optional) Button width set to ensure that all buttons have the same width | `string` | `undefined` |


## Events

| Event | Description | Type |
| ------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| `scale-click` | Emitted when button is clicked | `CustomEvent<{ id: string; selected: boolean; }>` |
| `scaleClick` | <span style="color:red">**[DEPRECATED]**</span> in v3 in favor of kebab-case event names<br/><br/> | `CustomEvent<{ id: string; selected: boolean; }>` |


## Methods

### `setFocus() => Promise<void>`



#### Returns

Type: `Promise<void>`




## Dependencies

### Depends on

- [scale-icon-action-success](../icons/action-success)

### Graph
```mermaid
graph TD;
scale-segmented-button --> scale-icon-action-success
style scale-segmented-button fill:#f9f,stroke:#333,stroke-width:4px
```

----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/**
* @license
* Scale https://github.com/telekom/scale
*
* Copyright (c) 2021 Egor Kirpichev and contributors, Deutsche Telekom AG
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

:host {
/* not selected */
--background-color-selected: var(--telekom-color-ui-base);
--button-radius: 6px;
--padding-top-bottom: var(--telekom-spacing-unit-x1);
--padding-left-right: var(--telekom-spacing-unit-x2);
--font-family: var(--telekom-typography-font-family-sans);
--font-weight: var(--telekom-typography-font-weight-bold);
--font-size: var(--telekom-typography-font-size-caption);
--label-disabled: var(--telekom-color-text-and-icon-disabled);
--transition: all var(--telekom-motion-duration-transition)
var(--telekom-motion-easing-standard);

--color-selected: var(--telekom-color-text-and-icon-primary-standard);

/* large styles */
--font-size-large: var(--telekom-typography-font-size-body);
--large-height: var(--telekom-spacing-unit-x6);

/* xl styles */
--font-size-xl: var(--telekom-typography-font-size-body);
--xl-height: var(--telekom-spacing-unit-x9);

/* hover styles */
--background-color-hover: var(--telekom-color-ui-state-fill-hovered);

/* active styles */
--background-color-active: var(--telekom-color-ui-state-fill-pressed);
--box-shadow-focus: 0 0 0 var(--telekom-line-weight-highlight)
var(--telekom-color-functional-focus);

display: flex;
align-items: center;
justify-content: center;
flex: 1;
}

.segmented-button {
box-sizing: border-box;
position: relative;
border: 0;
height: 100%;
width: 100%;
border-radius: var(--button-radius);
padding-top: var(--padding-top-bottom);
padding-bottom: var(--padding-top-bottom);
transition: var(--transition);
background-color: transparent;
padding: 0;
}

.segmented-button--mask {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
position: relative;
height: 100%;
width: 100%;
border-radius: var(--button-radius);
background-color: transparent;
font-family: var(--font-family);
font-size: var(--font-size);
font-weight: var(--font-weight);
transition: var(--transition);
white-space: nowrap;
color: var(--telekom-color-text-and-icon-standard);
}

button.segmented-button--icon-only.segmented-button--small .segmented-button--mask {
padding-left: var(--telekom-spacing-unit-x4);
padding-right: var(--telekom-spacing-unit-x4);
}

button.segmented-button--small .segmented-button--mask {
padding-left: var(--telekom-spacing-unit-x2);
padding-right: var(--telekom-spacing-unit-x3);
}

.segmented-button--large {
font-size: var(--font-size-large);
height: 36px;
}

button.segmented-button--large .segmented-button--mask {
padding-left: var(--telekom-spacing-unit-x3);
padding-right: var(--telekom-spacing-unit-x4);
}

.segmented-button--xl {
font-size: var(--font-size-large);
height: 40px;
}
button.segmented-button--xl .segmented-button--mask {
padding-left: 16px;
padding-right: 20px;
}

.segmented-button--selected {
background-color: var(--background-color-selected);
box-shadow: var(--telekom-shadow-raised-standard);
}

.segmented-button--selected .segmented-button--mask {
color: var(--color-selected);
}

.segmented-button--disabled.segmented-button--selected .segmented-button--mask {
color: var(--label-disabled);
}

.segmented-button--disabled:not(.segmented-button--selected) .segmented-button--mask{
color: var(--label-disabled);
background-color: transparent;
}

.segmented-button--left-sibling-selected {
border-radius: 0 var(--button-radius) var(--button-radius) 0;
margin-left: 0;
clip-path: inset(-44px -44px -44px 0px);
backface-visibility: hidden;
overflow: hidden;
}

.segmented-button--right-sibling-selected {
border-radius: var(--button-radius) 0 0 var(--button-radius);
clip-path: inset(-44px -0px -44px -44px);
margin-right: 0;
backface-visibility: hidden;
overflow: hidden;
position: relative;
}

.segmented-button--left-right-sibling-selected {
clip-path: inset(-44px 0px -44px -0px);
border-radius: 0;
margin-left: 0;
margin-right: 0;
backface-visibility: hidden;
}

.segmented-button:not(.segmented-button--disabled):hover .segmented-button--mask{
background-color: var(--background-color-hover);
}

.segmented-button:not(.segmented-button--disabled):active .segmented-button--mask{
background-color: var(--background-color-active);
}

.segmented-button:focus {
box-shadow: var(--telekom-shadow-raised-standard);
outline: var(--telekom-spacing-unit-x05) solid var(--telekom-color-functional-focus );
outline-offset: var(--telekom-spacing-unit-x025);
z-index: 40;
position: relative;
}

.segmented-button--left-right-sibling-selected:focus,
.segmented-button--right-sibling-selected:focus,
.segmented-button--left-sibling-selected:focus {
clip-path: none;
}

.segmented-button:not(.segmented-button--icon-only) scale-icon-action-success,
.segmented-button scale-icon-action-close {
margin-right: var(--telekom-spacing-unit-x1);
}

.segmented-button:not(.segmented-button--selected) scale-icon-action-success {
visibility: hidden !important;
}

.segmented-button scale-icon-action-success {
vertical-align: middle;
}

.segmented-button--selected:not(.segmented-button--icon-only) .icon-container {
display: none !important;
}

.segmented-button .success-icon-container {
display: none;
}

.segmented-button--selected .success-icon-container {
display: flex;
justify-content: center;
align-items: center;
}

.icon-container {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}

.segmented-button--selected .icon-container {
color: var(--color-selected);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* @license
* Scale https://github.com/telekom/scale
*
* Copyright (c) 2021 Egor Kirpichev and contributors, Deutsche Telekom AG
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import {
Component,
Prop,
h,
Host,
Element,
Event,
EventEmitter,
Method,
} from '@stencil/core';
import classNames from 'classnames';
import { emitEvent } from '../../utils/utils';
import statusNote from '../../utils/status-note';

let i = 0;

@Component({
tag: 'scale-segmented-button',
styleUrl: 'segmented-button.css',
shadow: true,
})
export class SegmentedButton {
@Element() hostElement: HTMLElement;
/** (optional) The size of the button */
@Prop() size?: 'small' | 'large' | 'xl' = 'small';
/** (optional) If `true`, the button is selected */
@Prop({ mutable: true }) selected?: boolean = false;
/** (optional) If `true`, the button is disabled */
@Prop() disabled?: boolean = false;
/** (optional) button's id */
@Prop({ reflect: true, mutable: true }) segmentedButtonId?: string;
/** (optional) aria-label attribute needed for icon-only buttons */
@Prop() ariaLabelSegmentedButton: string;
/** (optional) Button width set to ensure that all buttons have the same width */
@Prop() width?: string;
/** (optional) Injected CSS styles */
@Prop() styles?: string;
// /** (optional) */
@Prop({ reflect: true, mutable: true }) adjacentSiblings?:
| 'left'
| 'right'
| 'leftright';
/** (optional) translation of 'selected */
@Prop() ariaLangSelected? = 'selected';
/** (optional) translation of 'deselected */
@Prop() ariaLangDeselected? = 'deselected';
/** a11y text for getting meaningful value. `$buttonNumber` and `$selected` are template variables and will be replaces by their corresponding properties. */
@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;
/** Emitted when button is clicked */
@Event({ eventName: 'scale-click' }) scaleClick!: EventEmitter<{
id: string;
selected: boolean;
}>;
/** @deprecated in v3 in favor of kebab-case event names */
@Event({ eventName: 'scaleClick' }) scaleClickLegacy!: EventEmitter<{
id: string;
selected: boolean;
}>;

private focusableElement: HTMLElement;

@Method()
async setFocus() {
this.focusableElement.focus();
}

componentDidRender() {
if (this.hostElement.hasAttribute('aria-label')) {
statusNote({
tag: 'deprecated',
message:
'Property "ariaLabel" is deprecated. Please use the "ariaLabelSegmentedButton" property!',
type: 'warn',
source: this.hostElement,
});
}
}

componentWillLoad() {
if (this.segmentedButtonId == null) {
this.segmentedButtonId = 'segmented-button-' + 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;
}

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);
icon.setAttribute('size', '16');
icon.style.display = 'inline-flex';
icon.style.marginRight = '4px';
this.hasIcon = true;
}
if (child.nodeType == 3 && this.hostElement.childNodes.length == 1) {
this.textOnly = true;
var 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) => {
event.preventDefault();
this.selected = !this.selected;
emitEvent(this, 'scaleClick', {
id: this.segmentedButtonId,
selected: this.selected,
});
};

render() {
return (
<Host>
{this.styles && <style>{this.styles}</style>}
<button
ref={(el) => (this.focusableElement = el)}
class={this.getCssClassMap()}
id={this.segmentedButtonId}
onClick={this.handleClick}
disabled={this.disabled}
type="button"
style={{ width: this.width }}
aria-label={this.ariaLabelSegmentedButton}
aria-pressed={this.selected}
part={this.getBasePartMap()}
aria-description={this.getAriaDescriptionTranslation()}
>
<div class="segmented-button--mask">
{ !this.iconOnly && <div class="success-icon-container">
<scale-icon-action-success
size={14}
class="scale-icon-action-success"
accessibility-title="success"
/>
</div>}
<div class="icon-container">
<slot name="segmented-button-icon" />
</div>
<slot />
</div>
</button>
</Host>
);
}

getBasePartMap() {
return this.getCssOrBasePartMap('basePart');
}

getCssClassMap() {
return this.getCssOrBasePartMap('css');
}

getCssOrBasePartMap(mode: 'basePart' | 'css') {
const prefix = mode === 'basePart' ? '' : 'segmented-button--';

return classNames(
'segmented-button',
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`,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template>
<scale-segmented-button-group
:size="size"
:disabled="disabled"
:multi-select="multiSelect"
:required="required"
:label="label"
:helper-text="helperText"
:styles="styles"
@scaleChange="scaleChange"
>
<slot></slot>
</scale-segmented-button-group>
</template>

<script>
import { action } from "@storybook/addon-actions";
export default {
props: {
size: { type: String, default: 'small' },
disabled: { type: Boolean, default: false },
multiSelect: { type: Boolean, default: false },
required: {type: Boolean, default: false},
label: {type: String},
helperText: {type: String},
styles: { type: String },
},
methods: {
scaleChange($event) {
action("scaleChange");
this.$emit("scaleChange", $event);
},
'scale-change'($event) {
action("scale-change");
this.$emit("scale-change", $event);
},
}
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { Meta, Story, ArgsTable, Canvas } from '@storybook/addon-docs';
import ScaleSegmentedButtonGroup from './ScaleSegmentedButtonGroup.vue';

<Meta
title="Beta Components/Segmented Button"
component={ScaleSegmentedButtonGroup}
argTypes={{
size: {
control: { type: 'select', options: [ 'small', 'large', 'xl'] },
},
}}
/>

export const Template = (args, { argTypes }) => ({
components: { ScaleSegmentedButtonGroup },
props: {
label: String,
icon: String,
...ScaleSegmentedButtonGroup.props,
},
template: `
<scale-segmented-button-group
:size="size"
:disabled="disabled"
:multi-select="multiSelect"
:styles="styles"
:required="required"
:helper-text="helperText"
:label="label"
>
<scale-segmented-button selected>Apple</scale-segmented-button>
<scale-segmented-button>One+</scale-segmented-button>
<scale-segmented-button>Samsung</scale-segmented-button>
<scale-segmented-button>Huawei</scale-segmented-button>
</scale-segmented-button-group>
`,
});

export const MultiSelectTemplate = (args, { argTypes }) => ({
components: { ScaleSegmentedButtonGroup },
props: {
label: String,
icon: String,
...ScaleSegmentedButtonGroup.props,
},
template: `
<scale-segmented-button-group
:size="size"
:disabled="disabled"
:multi-select="multiSelect"
:styles="styles"
>
<scale-segmented-button selected>Apple</scale-segmented-button>
<scale-segmented-button selected>One+</scale-segmented-button>
<scale-segmented-button>Samsung</scale-segmented-button>
<scale-segmented-button>Huawei</scale-segmented-button>
</scale-segmented-button-group>
`,
});

export const IconOnlyTemplate = (args, { argTypes }) => ({
components: { ScaleSegmentedButtonGroup },
props: {
label: String,
icon: String,
...ScaleSegmentedButtonGroup.props,
},
template: `
<scale-segmented-button-group
:size="size"
:disabled="disabled"
:multi-select="multiSelect"
:styles="styles"
>
<scale-segmented-button selected><scale-icon-weather-cloudy slot="segmented-button-icon"></scale-icon-weather-cloudy></scale-segmented-button>
<scale-segmented-button><scale-icon-weather-rain slot="segmented-button-icon"></scale-icon-weather-rain></scale-segmented-button>
<scale-segmented-button><scale-icon-weather-sunny slot="segmented-button-icon"></scale-icon-weather-sunny></scale-segmented-button>
<scale-segmented-button><scale-icon-weather-heavy-snow slot="segmented-button-icon"></scale-icon-weather-heavy-snow></scale-segmented-button>
</scale-segmented-button-group>
`,
});

export const IconTextTemplate = (args, { argTypes }) => ({
components: { ScaleSegmentedButtonGroup },
props: {
label: String,
icon: String,
...ScaleSegmentedButtonGroup.props,
},
template: `
<scale-segmented-button-group
:size="size"
:disabled="disabled"
:multi-select="multiSelect"
:styles="styles"
>
<scale-segmented-button selected><scale-icon-device-device-phone slot="segmented-button-icon"></scale-icon-device-device-phone>Apple</scale-segmented-button>
<scale-segmented-button><scale-icon-device-device-phone slot="segmented-button-icon"></scale-icon-device-device-phone>One+</scale-segmented-button>
<scale-segmented-button><scale-icon-device-device-phone slot="segmented-button-icon"></scale-icon-device-device-phone>Samsung</scale-segmented-button>
<scale-segmented-button><scale-icon-device-device-phone slot="segmented-button-icon"></scale-icon-device-device-phone>Huawei</scale-segmented-button>
</scale-segmented-button-group>
`,
});

## Standard

<Canvas withSource="none">
<Story name="Standard" args={{label: "Group Label"}}>
{Template.bind({})}
</Story>
</Canvas>

<ArgsTable story="Standard" />

```html
<scale-segmented-button-group>
<scale-segmented-button selected>Apple</scale-segmented-button>
<scale-segmented-button>One+</scale-segmented-button>
<scale-segmented-button>Samsung</scale-segmented-button>
<scale-segmented-button>Huawei</scale-segmented-button>
</scale-segmented-button-group>
```

## Multi Select

<Canvas withSource="none">
<Story name="MultiSelect" args={{multiSelect: true, label: "Group Label"}}>
{MultiSelectTemplate.bind({})}
</Story>
</Canvas>

```html
<scale-segmented-button-group multi-select>
<scale-segmented-button selected>Apple</scale-segmented-button>
<scale-segmented-button selected>One+</scale-segmented-button>
<scale-segmented-button>Samsung</scale-segmented-button>
<scale-segmented-button>Huawei</scale-segmented-button>
</scale-segmented-button-group>
```

## Icon only

<Canvas withSource="none">
<Story name="Icon only" args={{label: "Group Label"}}>
{IconOnlyTemplate.bind({})}
</Story>
</Canvas>

```html
<scale-segmented-button-group>
<scale-segmented-button selected><scale-icon-weather-cloudy slot="segmented-button-icon"></scale-icon-weather-cloudy></scale-segmented-button>
<scale-segmented-button><scale-icon-weather-rain slot="segmented-button-icon"></scale-icon-weather-rain></scale-segmented-button>
<scale-segmented-button><scale-icon-weather-sunny slot="segmented-button-icon"></scale-icon-weather-sunny></scale-segmented-button>
<scale-segmented-button><scale-icon-weahter-heavy-snow slot="segmented-button-icon"></scale-icon-weahter-heavy-snow></scale-segmented-button>
</scale-segmented-button-group>
```

## Icon and Text

<Canvas withSource="none">
<Story name="Icon and Text" args={{label: "Group Label"}}>
{IconTextTemplate.bind({})}
</Story>
</Canvas>

```html
<scale-segmented-button-group>
<scale-segmented-button><scale-icon-device-device-phone slot="segmented-button-icon"></scale-icon-device-device-phone>Apple</scale-segmented-button>
<scale-segmented-button><scale-icon-device-device-phone slot="segmented-button-icon"></scale-icon-device-device-phone>One+</scale-segmented-button>
<scale-segmented-button selected><scale-icon-device-device-phone slot="segmented-button-icon"></scale-icon-device-device-phone>Samsung</scale-segmented-button>
<scale-segmented-button selected><scale-icon-device-device-phone slot="segmented-button-icon"></scale-icon-device-device-phone>Huawei</scale-segmented-button>
</scale-segmented-button-group>
```
Empty file.
Empty file.