diff --git a/packages/components/src/components/callout/callout.css b/packages/components/src/components/callout/callout.css index 20edf7d739..9d66fa5052 100644 --- a/packages/components/src/components/callout/callout.css +++ b/packages/components/src/components/callout/callout.css @@ -1,95 +1,71 @@ :host { - --inner-width-small: var(--telekom-spacing-unit-x20); - --inner-height-small: var(--telekom-spacing-unit-x20); - --inner-width-large: 126.5px; - --inner-height-large: 96px; - --width-small: 120px; - --height-small: 120px; - --height-large: 160px; - --width-large: 160px; - /* FIXME variables shouldn't have color names */ - --color-blue: var(--telekom-color-functional-informational-standard); - /* FIXME variables are used sometimes as background and sometimes as font color */ - --color-white: var(--telekom-color-background-surface); - --color-black: var(--telekom-color-text-and-icon-functional-black); - --color-primary: var(--telekom-color-primary-standard); - --font-family: var(--telekom-typography-font-family-sans); - /* FIXME is this calc necessary? */ - --font-size: calc(var(--telekom-typography-font-size-callout) * 3); - --font-size-prefix: var(--telekom-typography-font-size-callout); -} + --position: absolute; + --background: var(--telekom-color-ui-extra-strong); + --color: var(--telekom-color-text-and-icon-inverted-standard); + --spacing: var(--telekom-spacing-unit-x6); + --min-width: 6rem; + --aspect-ratio: 1; + --rotation: 0deg; + --font-standard: var(--telekom-text-style-lead-text); + --font-small: var(--telekom-text-style-body-bold); + --font-large: var(--telekom-text-style-heading-1); -.callout { - display: flex; - justify-content: center; + box-sizing: border-box; + display: inline-flex; align-items: center; - border-radius: 100%; - background: var(--color-primary); - font-family: var(--font-family); - color: var(--color-white); + justify-content: center; text-align: center; + border-radius: 50%; + position: var(--position); + background: var(--background); + color: var(--color); + min-width: var(--min-width); + aspect-ratio: var(--aspect-ratio); + transform: rotateZ(var(--rotation, 0deg)); } -.callout.callout--color-primary { - background: var(--color-primary); -} -.callout.callout--color-white { - background: var(--color-white); - color: var(--color-black); -} -.callout.callout--color-blue { - background: var(--color-blue); - color: var(--telekom-color-text-and-icon-white-standard); -} -.callout.callout--color-black { - background: var(--telekom-color-ui-extra-strong); - color: var(--telekom-color-text-and-icon-inverted-standard); +[part='base'] { + box-sizing: border-box; + padding: var(--spacing); + font: var(--font-standard); + font-weight: var(--telekom-typography-font-weight-bold); + line-height: var(--telekom-typography-line-spacing-tight); } -.callout.callout--size-large { - width: var(--width-large); - height: var(--height-large); -} +/* Color variants */ -.callout.callout--size-small { - width: var(--width-small); - height: var(--height-small); +:host([variant='primary']) { + --background: var(--telekom-color-primary-standard); + --color: var(--telekom-color-text-and-icon-white-standard); } -.callout.callout--size-large .callout__inner { - width: var(--inner-width-large); - height: var(--inner-height-large); +:host([variant='black']) { + --background: var(--telekom-color-ui-black, #000000); + --color: var(--telekom-color-text-and-icon-white-standard); } -.callout.callout--size-small .callout__inner { - width: var(--inner-width-small); - height: var(--inner-height-small); +:host([variant='white']) { + --background: var(--telekom-color-ui-white, #ffffff); + --color: var(--telekom-color-text-and-icon-black-standard); } -.callout__inner { - transform: rotateZ(var(--rotation)); - overflow: hidden; +:host([variant='blue']) { + --background: var(--telekom-color-additional-cyan-400); + --color: var(--telekom-color-text-and-icon-black-standard); } -.callout__prefix { - font-size: var(--font-size-prefix); - font-weight: 400; -} +/* Enforce "line break" for every direct child of scale-callout */ -.callout__text { - font-size: var(--font-size); - font-weight: 700; +::slotted(*) { + display: block; } -.callout__sup { - font-size: 30px; - cursor: help; -} +/* Font style options (medium is default) */ -.callout.callout--size-large.callout--asterisk .callout__prefix { - margin-bottom: 10px; +::slotted(.scl-callout-text-small) { + font: var(--font-small); } -.callout.callout--size-small .callout__prefix { - margin-bottom: 10px; +::slotted(.scl-callout-text-large) { + font: var(--font-large); } diff --git a/packages/components/src/components/callout/callout.spec.ts b/packages/components/src/components/callout/callout.spec.ts deleted file mode 100644 index f1c3944b47..0000000000 --- a/packages/components/src/components/callout/callout.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { newSpecPage } from '@stencil/core/testing'; -import { Callout } from './callout'; - -it('should reflect attributes/props', async () => { - const page = await newSpecPage({ - components: [Callout], - html: ` - `, - }); - - expect(page.rootInstance.size).toBe('large'); - expect(page.rootInstance.variant).toBe('primary'); - expect(page.rootInstance.rotation).toBe(15); -}); - -it('checks another variant, other than prop', async () => { - const page = await newSpecPage({ - components: [Callout], - html: ` - `, - }); - - expect(page.rootInstance.size).toBe('large'); - expect(page.rootInstance.variant).toBe('red'); - expect(page.rootInstance.rotation).toBe(15); -}); diff --git a/packages/components/src/components/callout/callout.tsx b/packages/components/src/components/callout/callout.tsx index 3173fb9995..a07b22c7c6 100644 --- a/packages/components/src/components/callout/callout.tsx +++ b/packages/components/src/components/callout/callout.tsx @@ -1,7 +1,14 @@ -import { Component, h, Host, Prop, Element } from '@stencil/core'; -import classNames from 'classnames'; +import { Component, h, Host, Prop, Element, Watch } from '@stencil/core'; import statusNote from '../../utils/status-note'; +/** + * Adds the `px` suffix to a string number + * but leaves other units untouched. + * 1 -> 1px + * 5% -> 5% + */ +const numToPx = (val: string) => (Number.isNaN(Number(val)) ? val : val + 'px'); + @Component({ tag: 'scale-callout', styleUrl: 'callout.css', @@ -9,67 +16,95 @@ import statusNote from '../../utils/status-note'; }) export class Callout { @Element() hostElement: HTMLElement; - /** (optional) Variant size of the callout itself */ - @Prop({ mutable: true }) size: 'large' | 'small' = 'large'; - /** (optional) Variant filling of the callout */ - @Prop({ mutable: true }) variant: 'primary' | 'white' | 'black' | 'blue'; - /** (optional) Variant rotation of the callout/circle */ - @Prop({ mutable: true }) rotation: number = 0; - /** (optional) text when hovering with asterisk */ - @Prop({ mutable: true }) asterisk: string; + + baseEl: HTMLElement; + mo: MutationObserver; + + /** (optional) Color variant of the callout */ + @Prop() variant?: 'primary' | 'blue' | 'white' | 'black' | string; + /** (optional) Degree of rotation */ + @Prop() rotation?: number = 0; + /** (optional) CSS `top` value for absolute position */ + @Prop() top?: string; + /** (optional) CSS `right` value for absolute position */ + @Prop() right?: string; + /** (optional) CSS `bottom` value for absolute position */ + @Prop() bottom?: string; + /** (optional) CSS `left` value for absolute position */ + @Prop() left?: string; + /** (optional) Injected CSS styles */ + @Prop() styles?: string; connectedCallback() { statusNote({ source: this.hostElement, tag: 'beta' }); + this.syncPropsToCSS(); } - displayStyle() { - return `:host { - --rotation: ${this.rotation}deg; - }`; + componentDidLoad() { + const observer = new MutationObserver(() => { + this.adjustSize(); + }); + observer.observe(this.hostElement, { + attributes: false, + childList: true, + subtree: true, + characterData: true, + }); + this.mo = observer; + // Wait for full styles before measuring + window.requestAnimationFrame(this.adjustSize); } - render() { - return ( - - -
-
-
- -
-
- - - - {this.asterisk && ( - - * - - )} -
-
-
-
- ); + disconnectedCallback() { + if (this.mo) { + this.mo.disconnect(); + } } - getBasePartMap() { - return this.getCssOrBasePartMap('basePart'); + @Watch('rotation') + @Watch('top') + @Watch('right') + @Watch('bottom') + @Watch('left') + rotationChanged() { + this.syncPropsToCSS(); } - getCssClassMap() { - return this.getCssOrBasePartMap('css'); - } + /** + * `aspect-ratio` is not enough when dealing with text :( + */ + adjustSize = () => { + const { width, height } = this.baseEl.getBoundingClientRect(); + const largest = Math.max(width, height); + this.hostElement.style.setProperty('--min-width', `${largest}px`); + }; + + syncPropsToCSS() { + this.hostElement.style.setProperty('--rotation', `${this.rotation}deg`); - getCssOrBasePartMap(mode: 'basePart' | 'css') { - const name = 'callout'; - const prefix = mode === 'basePart' ? '' : `${name}--`; + if ( + this.top != null || + this.right != null || + this.bottom != null || + this.left != null + ) { + Object.assign(this.hostElement.style, { + top: numToPx(this.top), + right: numToPx(this.right), + bottom: numToPx(this.bottom), + left: numToPx(this.left), + }); + } + } - return classNames( - name, - this.variant && `${prefix}color-${this.variant}`, - this.size && `${prefix}size-${this.size}`, - this.asterisk && `${prefix}asterisk` + render() { + return ( + + {this.styles && } +
(this.baseEl = el)}> + +
+
); } } diff --git a/packages/components/src/components/callout/readme.md b/packages/components/src/components/callout/readme.md index 6655776f24..2aed121e29 100644 --- a/packages/components/src/components/callout/readme.md +++ b/packages/components/src/components/callout/readme.md @@ -7,19 +7,22 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ---------- | ---------- | ------------------------------------------------- | ------------------------------------------- | ----------- | -| `asterisk` | `asterisk` | (optional) text when hovering with asterisk | `string` | `undefined` | -| `rotation` | `rotation` | (optional) Variant rotation of the callout/circle | `number` | `0` | -| `size` | `size` | (optional) Variant size of the callout itself | `"large" \| "small"` | `'large'` | -| `variant` | `variant` | (optional) Variant filling of the callout | `"black" \| "blue" \| "primary" \| "white"` | `undefined` | +| Property | Attribute | Description | Type | Default | +| ---------- | ---------- | --------------------------------------------------- | -------- | ----------- | +| `bottom` | `bottom` | (optional) CSS `bottom` value for absolute position | `string` | `undefined` | +| `left` | `left` | (optional) CSS `left` value for absolute position | `string` | `undefined` | +| `right` | `right` | (optional) CSS `right` value for absolute position | `string` | `undefined` | +| `rotation` | `rotation` | (optional) Degree of rotation | `number` | `0` | +| `styles` | `styles` | (optional) Injected CSS styles | `string` | `undefined` | +| `top` | `top` | (optional) CSS `top` value for absolute position | `string` | `undefined` | +| `variant` | `variant` | (optional) Color variant of the callout | `string` | `undefined` | ## Shadow Parts -| Part | Description | -| --------- | ----------- | -| `"inner"` | | +| Part | Description | +| -------- | ----------- | +| `"base"` | | ---------------------------------------------- diff --git a/packages/storybook-vue/stories/components/callout/Callout.stories.mdx b/packages/storybook-vue/stories/components/callout/Callout.stories.mdx index 64ef539970..1f5fa6b354 100644 --- a/packages/storybook-vue/stories/components/callout/Callout.stories.mdx +++ b/packages/storybook-vue/stories/components/callout/Callout.stories.mdx @@ -7,31 +7,41 @@ import { } from '@storybook/addon-docs'; import ScaleCallout from './ScaleCallout.vue'; +export const CANVAS_HEIGHT = 180 + -export const Template = (args, { argTypes }) => ({ - props: { ...ScaleCallout.props, prefix: String, text: String }, - template: ` - -
{{ prefix }}
- 15 € -
- `, -}); - -export const TemplateSmall = (args, { argTypes }) => ({ - props: { ...ScaleCallout.props, prefix: String }, - template: ` - -
{{ prefix }}
- 15 € -
- `, -}); - -export const TemplateAsterisk = (args, { argTypes }) => ({ - props: { ...ScaleCallout.props, prefix: String }, - template: ` - -
{{ prefix }}
- 15 € -
- `, -}); -
({ ## Standard -### All default values - - {Template.bind({})} + {{ + props: { + ...ScaleCallout.props, + }, + template: ` +
+ + Now only + 15€ + +
+ ` + }}
```html - -
Für nur
- 15 € + + Now only + 15€ ``` @@ -126,114 +104,171 @@ export const TemplateAsterisk = (args, { argTypes }) => ({ ```css :host { - --inner-width-small: var(--telekom-spacing-unit-x20); - --inner-height-small: var(--telekom-spacing-unit-x20); - --inner-width-large: 126.5px; - --inner-height-large: 96px; - --width-small: 120px; - --height-small: 120px; - --height-large: 160px; - --width-large: 160px; - --color-blue: var(--telekom-color-functional-informational-standard); - --color-white: var(--telekom-color-background-surface); - --color-black: var(--telekom-color-text-and-icon-functional-black); - --color-primary: var(--telekom-color-primary-standard); - --font-family: var(--telekom-typography-font-family-sans); - --font-size: calc(var(--telekom-typography-font-size-callout) * 3); - --font-size-prefix: var(--telekom-typography-font-size-callout); -} + --position: absolute; + --background: var(--telekom-color-ui-extra-strong); + --color: var(--telekom-color-text-and-icon-inverted-standard); + --spacing: var(--telekom-spacing-unit-x6); + --min-width: 6rem; + --aspect-ratio: 1; + --rotation: 0deg; + --font-standard: var(--telekom-text-style-lead-text); + --font-small: var(--telekom-text-style-body-bold); + --font-large: var(--telekom-text-style-heading-1); } ``` -## Size +## Variants -There are two sizes: -Large / small. Large has the size 160x160 and small 120x120. In the example below, the callout is small +### Primary - + {{ + template: ` +
+ + Now only + 15€ + +
+ ` }} - > - {TemplateSmall.bind({})}
```html - -
Für nur
- 15 € + + Now only + 15€ ``` -## Rotation +### Black - + {{ + template: ` +
+ + Now only + 15€ + +
+ ` }} - > - {Template.bind({})}
```html - -
Für nur
- 15 € + + Now only + 15€ ``` -## Variants +### White -There are 4 standard colors (Primary - default, black, white, blue). The user also has the possibilty to determine another color by accessing the specific css class. + + + {{ + template: ` +
+ + Now only + 15€ + +
+ ` + }} +
+
+ +```html + + Now only + 15€ + +``` + +### Blue - + {{ + template: ` +
+ + Now only + 15€ + +
+ ` }} - > - {Template.bind({})}
```html -
Für nur
- 15 € + Now only + 15€
``` -## Asterisk +## Font Sizes + +### Medium - + {{ + template: ` +
+ + Bis zu 200 € + Tauschprämie + + Altgerätewert + erhalten + +
+ ` }} - > - {TemplateAsterisk.bind({})}
```html - -
Für nur
- 15 € + + Bis zu 200 € + Tauschprämie + + Altgerätewert + erhalten + +``` + +### Large and Small + +For convenience, two scoped CSS classes are provided for large and small text: `scl-callout-text-large` and `scl-callout-text-small`. But any size can be set using CSS. + + + + {{ + template: ` +
+ + LOREM + ipsum + dolor sit amet + +
+ ` + }} +
+
+ +```html + + LOREM + ipsum + dolor sit amet ``` diff --git a/packages/storybook-vue/stories/components/callout/ScaleCallout.vue b/packages/storybook-vue/stories/components/callout/ScaleCallout.vue index f4860cbd02..896d4997cc 100644 --- a/packages/storybook-vue/stories/components/callout/ScaleCallout.vue +++ b/packages/storybook-vue/stories/components/callout/ScaleCallout.vue @@ -1,23 +1,16 @@ - - - + render() { + return this.$slots.default + } +} + \ No newline at end of file diff --git a/packages/storybook-vue/stories/components/callout/callout.md b/packages/storybook-vue/stories/components/callout/callout.md index 54fbfdea7c..c7dec509c9 100644 --- a/packages/storybook-vue/stories/components/callout/callout.md +++ b/packages/storybook-vue/stories/components/callout/callout.md @@ -1,10 +1,68 @@
-

Callout

- Beta Component +

Callout (DRAFT)

+ Accessible AA
-A Callout Component provides a way for users to highlight important content visually. +A Callout component provides a way for users to highlight important content visually. -## Beta Component +## General -This component is still in the beta phase. When testing it, keep in mind that it may not have gone through all quality control measures, and it may not yet have WCAG accessibility certification. There may be changes to this component in the future. +### When to use + +- Mainly used in marketing assets to highlight very specific information on the page, in order to differentiate it from the surrounding content + - For example, to state and highlight prices + - for numbers up to 5 characters + - For short sentences, numbers with short text + +### When not to use + +- Use callouts sparingly for them to maintain effectiveness +- Do not use multiple callouts on the same section/ page/ topic - this would district the user and lessen the importance of the callout +- When longer text is needed +- (maybe mention max characters to be used) + +## Variants + +### Colors + +There are 5 color variants: + +- **Standard:** inverts the colors in dark and light mode +- **Magenta, Blue:** Stay the same in dark and light mode +- **Black, White:** To be used on images, stay the same in dark and light mode + +### Text Size Variants + +There are 3 text sizes to offer the most flexibility for different types of content. + +It is recommended to use the medium text size for sentences that have only one level of hierarchy. + +It is recommended to use the small and large text sizes for information that requires two levels of hierarchy. + +For both options, the bubble size adjusts to the content. + +### Rotation + +The callout can be rotated for specific use cases. We recommend using it sparingly. + +## Accessibility + +### Screen Readers + +… + +### Disabled State + +There is no disabled state for the Callout. + +## Elements + +- + +## Best Practice + +(maybe add max size of the circle later) + +## Related components + +… diff --git a/packages/storybook-vue/stories/components/callout/callout_de.md b/packages/storybook-vue/stories/components/callout/callout_de.md index e75a0c833d..77931d9fa7 100644 --- a/packages/storybook-vue/stories/components/callout/callout_de.md +++ b/packages/storybook-vue/stories/components/callout/callout_de.md @@ -1,10 +1,6 @@

Callout

- Beta Component + Accessible AA
-Eine Callout Component bietet die Möglichkeit, wichtigen Inhalt für den Nutzer visuell hervorzuheben. - -## Beta-Komponente - -Diese Komponente befindet sich noch im Beta-Stadium. Wenn du sie testest, bedenke, dass sie möglicherweise noch nicht alle Qualitätskontrollmaßnahmen durchlaufen hat und noch keine WCAG-Zertifizierung zur Barrierefreiheit vorliegt. In Zukunft kann es zu Änderungen an dieser Komponente kommen. +TODO diff --git a/packages/visual-tests/src/callout.visual.spec.js b/packages/visual-tests/src/callout.visual.spec.js index 89460cc2ed..6fff987e2f 100644 --- a/packages/visual-tests/src/callout.visual.spec.js +++ b/packages/visual-tests/src/callout.visual.spec.js @@ -5,10 +5,12 @@ describe('Callout', () => { }); test.each([ ['standard'], - ['size'], - ['rotation'], - ['variants'], - ['asterisk'], + ['primary'], + ['black'], + ['white'], + ['blue'], + ['medium'], + ['large-and-small'], ])('%p', async (variant) => { await global.runSetup(`beta-components-callout--${variant}`); await global.visualCheck();