Skip to content

Commit e6807b0

Browse files
authored
refactor(heading): migrate to skeleton blueprint architecture (#9677)
2 parents f4bd861 + add7e8e commit e6807b0

File tree

17 files changed

+233
-121
lines changed

17 files changed

+233
-121
lines changed

packages/components/src/components/component-list.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { KolDialogWc } from './dialog/component';
1515
import { KolDialog } from './dialog/shadow';
1616
import { KolDrawer } from './drawer/shadow';
1717
import { KolForm } from './form/shadow';
18-
import { KolHeading } from './heading/shadow';
18+
import { KolHeading } from './heading/component';
1919
import { KolIcon } from './icon/component';
2020
import { KolImage } from './image/component';
2121
import { KolInputCheckbox } from './input-checkbox/shadow';

packages/components/src/components/heading/test/__snapshots__/snapshot.spec.tsx.snap renamed to packages/components/src/components/heading/__snapshots__/snapshot.spec.tsx.snap

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ exports[`kol-heading should render with _label="Headline" _level=6 1`] = `
7777
</kol-heading>
7878
`;
7979

80+
exports[`kol-heading should render with _label="Headline" _secondaryHeadline="Subheadline" 1`] = `
81+
<kol-heading>
82+
<template shadowrootmode="open">
83+
<hgroup class="kol-heading-group">
84+
<strong class="kol-headline kol-headline--group kol-headline--primary kol-headline--strong">
85+
Headline
86+
<slot name="expert" slot="expert"></slot>
87+
</strong>
88+
<p class="kol-headline kol-headline--group kol-headline--secondary">
89+
Subheadline
90+
</p>
91+
</hgroup>
92+
</template>
93+
</kol-heading>
94+
`;
95+
8096
exports[`kol-heading should render with _label="Headline" 1`] = `
8197
<kol-heading>
8298
<template shadowrootmode="open">
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type { JSX } from '@stencil/core';
2+
import { Component, h, Host, Prop, Watch } from '@stencil/core';
3+
4+
import { BaseWebComponent } from '../../internal/functional-components/base-web-component';
5+
import type { WebComponentInterface } from '../../internal/functional-components/generic-types';
6+
import type { HeadingApi } from '../../internal/functional-components/heading/api';
7+
import { HeadingFC } from '../../internal/functional-components/heading/component';
8+
import { HeadingController } from '../../internal/functional-components/heading/controller';
9+
import type { HeadingLevel, LabelWithExpertSlotPropType } from '../../schema';
10+
11+
/**
12+
*
13+
* @slot - Inhalt der Überschrift.
14+
*/
15+
@Component({
16+
tag: 'kol-heading',
17+
styleUrls: {
18+
default: './style.scss',
19+
},
20+
shadow: true,
21+
})
22+
export class KolHeading extends BaseWebComponent<HeadingApi> implements WebComponentInterface<HeadingApi> {
23+
private readonly ctrl: HeadingController = new HeadingController(this.setState, this.getState);
24+
25+
/**
26+
* Defines the visible or semantic label of the component (e.g. aria-label, label, headline, caption, summary, etc.). Set to `false` to enable the expert slot.
27+
*/
28+
@Prop() public _label!: LabelWithExpertSlotPropType;
29+
30+
@Watch('_label')
31+
public watchLabel(value?: LabelWithExpertSlotPropType): void {
32+
this.ctrl.watchLabel(value);
33+
}
34+
35+
/**
36+
* Defines which H-level from 1-6 the heading has. 0 specifies no heading and is shown as bold text.
37+
*/
38+
@Prop() public _level?: HeadingLevel = 0;
39+
40+
@Watch('_level')
41+
public watchLevel(value?: HeadingLevel): void {
42+
this.ctrl.watchLevel(value);
43+
}
44+
45+
/**
46+
* Defines the text of the secondary headline.
47+
*/
48+
@Prop() public _secondaryHeadline?: string;
49+
50+
@Watch('_secondaryHeadline')
51+
public watchSecondaryHeadline(value?: string): void {
52+
this.ctrl.watchSecondaryHeadline(value);
53+
}
54+
55+
public componentWillLoad(): void {
56+
this.ctrl.componentWillLoad({
57+
label: this._label,
58+
level: this._level,
59+
secondaryHeadline: this._secondaryHeadline,
60+
});
61+
}
62+
63+
public render(): JSX.Element {
64+
return (
65+
<Host>
66+
<HeadingFC
67+
label={this.ctrl.getRenderProp('label')}
68+
level={this.ctrl.getRenderProp('level')}
69+
secondaryHeadline={this.ctrl.getRenderProp('secondaryHeadline')}
70+
/>
71+
</Host>
72+
);
73+
}
74+
}

packages/components/src/components/heading/shadow.tsx

Lines changed: 0 additions & 72 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { KolHeadingTag } from '../../core/component-names';
2+
import type { HeadingProps } from '../../schema';
3+
import { executeSnapshotTests } from '../../utils/testing';
4+
5+
import { KolHeading } from './component';
6+
7+
executeSnapshotTests<HeadingProps>(
8+
KolHeadingTag,
9+
[KolHeading],
10+
[
11+
{ _label: 'Headline' },
12+
{ _label: 'Headline', _secondaryHeadline: 'Subheadline' },
13+
...[0, 1, 2, 3, 4, 5, 6].map((_level) => ({ _label: 'Headline', _level }) as HeadingProps),
14+
],
15+
);

packages/components/src/components/heading/test/snapshot.spec.tsx

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1-
import type { HeadingLevel } from '../../schema';
2-
import { validateLevel } from '../../schema';
3-
41
import type { Generic } from 'adopted-style-sheets';
52

3+
import type { HeadingLevel } from '../../internal/props/level';
4+
import { headingLevelOptions } from '../../internal/props/level';
5+
import { watchValidator } from '../../schema/utils';
6+
67
export const watchHeadingLevel = (component: Generic.Element.Component, value?: HeadingLevel): void => {
7-
validateLevel(component, value);
8+
watchValidator(
9+
component,
10+
'_level',
11+
(value?: HeadingLevel): boolean => typeof value === 'number' && headingLevelOptions.includes(value),
12+
new Set(headingLevelOptions.map(String)),
13+
value,
14+
{
15+
defaultValue: 1,
16+
required: true,
17+
},
18+
);
819
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { labelWithExpertSlotProp, levelProp, secondaryHeadlineProp } from '../../props';
2+
import type { ApiFromConfig, PropsConfigShape } from '../generic-types';
3+
4+
export const headingPropsConfig = {
5+
optional: [levelProp, secondaryHeadlineProp],
6+
required: [labelWithExpertSlotProp],
7+
} as const satisfies PropsConfigShape;
8+
9+
export type HeadingApi = ApiFromConfig<typeof headingPropsConfig>;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { FunctionalComponent as FC } from '@stencil/core';
2+
import { h } from '@stencil/core';
3+
4+
import clsx from '../../../utils/clsx';
5+
import type { HeadingLevel } from '../../props';
6+
import type { FunctionalComponentProps } from '../generic-types';
7+
import type { HeadingApi } from './api';
8+
9+
type HeadlineTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'strong';
10+
11+
function getHeadlineTag(level: HeadingLevel | number): HeadlineTag {
12+
return level >= 1 && level <= 6 ? (`h${level}` as HeadlineTag) : 'strong';
13+
}
14+
15+
export const HeadingFC: FC<FunctionalComponentProps<HeadingApi>> = (props) => {
16+
const { label, level, secondaryHeadline } = props;
17+
const HeadlineTag = getHeadlineTag(level);
18+
19+
if (!secondaryHeadline) {
20+
return (
21+
<HeadlineTag class={clsx('kol-headline', `kol-headline--${HeadlineTag}`, 'kol-headline--single')}>
22+
{label}
23+
<slot name="expert" slot="expert" />
24+
</HeadlineTag>
25+
);
26+
}
27+
28+
return (
29+
<hgroup class="kol-heading-group">
30+
<HeadlineTag class={clsx('kol-headline', `kol-headline--${HeadlineTag}`, 'kol-headline--group', 'kol-headline--primary')}>
31+
{label}
32+
<slot name="expert" slot="expert" />
33+
</HeadlineTag>
34+
<p class="kol-headline kol-headline--group kol-headline--secondary">{secondaryHeadline}</p>
35+
</hgroup>
36+
);
37+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { labelWithExpertSlotProp, levelProp, secondaryHeadlineProp } from '../../props';
2+
import { BaseController } from '../base-controller';
3+
import type { ControllerInterface, GetStateFn, ResolvedInputProps, SetStateFn } from '../generic-types';
4+
import type { HeadingApi } from './api';
5+
import { headingPropsConfig } from './api';
6+
7+
export class HeadingController extends BaseController<HeadingApi> implements ControllerInterface<HeadingApi> {
8+
public constructor(setState: SetStateFn<HeadingApi>, getState: GetStateFn<HeadingApi>) {
9+
super(headingPropsConfig, setState, getState);
10+
}
11+
12+
public componentWillLoad(props: ResolvedInputProps<HeadingApi>): void {
13+
const { label, level, secondaryHeadline } = props;
14+
this.watchLabel(label);
15+
this.watchLevel(level);
16+
this.watchSecondaryHeadline(secondaryHeadline);
17+
}
18+
19+
public watchLabel(value?: string): void {
20+
labelWithExpertSlotProp.apply(value, (v) => {
21+
this.setRenderProp('label', v);
22+
});
23+
}
24+
25+
public watchLevel(value?: number): void {
26+
levelProp.apply(value, (v) => {
27+
this.setRenderProp('level', v);
28+
});
29+
}
30+
31+
public watchSecondaryHeadline(value?: string): void {
32+
secondaryHeadlineProp.apply(value, (v) => {
33+
this.setRenderProp('secondaryHeadline', v);
34+
});
35+
}
36+
}

0 commit comments

Comments
 (0)