Skip to content

Commit 4db0def

Browse files
committed
feat: Implement BEM structure for Alert and Icon components
- Added BEM class generation for the `kol-alert` component, defining elements and modifiers. - Created a new `bem.ts` file for the `kol-icon` component to manage BEM class names. - Updated `shadow.tsx` and `Alert.tsx` to utilize BEM class names for styling. - Refactored Alert component to use dynamic BEM class names based on props. - Adjusted SCSS mixins and styles to align with the new BEM structure. - Updated snapshots to reflect changes in class names and structure. - Introduced a script to generate SCSS files based on BEM definitions. - Updated package dependencies to include `typed-bem` for BEM class generation. Refs: #7778
1 parent 9ba6f07 commit 4db0def

File tree

29 files changed

+589
-395
lines changed

29 files changed

+589
-395
lines changed

packages/components/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"query-selector-all-shadow-root": "0.0.3",
8585
"query-selector-shadow-root": "0.0.3",
8686
"rgba-convert": "0.3.0",
87+
"typed-bem": "1.0.0-rc.7",
8788
"wcag-contrast": "3.0.0"
8889
},
8990
"devDependencies": {

packages/components/src/components/alert/test/__snapshots__/snapshot.spec.tsx.snap

Lines changed: 300 additions & 300 deletions
Large diffs are not rendered by default.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { generateBemClassNames } from 'typed-bem';
2+
3+
type SCHEMA = {
4+
'kol-icon': {
5+
elements: {
6+
icon: {
7+
modifiers: null;
8+
};
9+
};
10+
modifiers: null;
11+
};
12+
};
13+
14+
const bem = generateBemClassNames<SCHEMA>();
15+
16+
const BEM: SCHEMA = {
17+
'kol-icon': {
18+
elements: {
19+
icon: { modifiers: null },
20+
},
21+
modifiers: null,
22+
},
23+
};
24+
25+
/**
26+
* Define the static BEM class names for the alert component.
27+
*/
28+
const BEM_CLASS_ICON = bem('kol-icon');
29+
const BEM_CLASS_ICON__ICON = bem('kol-icon', 'icon');
30+
31+
export { bem as genBemAlert, BEM as BEM_ICON };
32+
export { BEM_CLASS_ICON, BEM_CLASS_ICON__ICON };

packages/components/src/components/icon/shadow.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { Component, h, Host, Prop, State, Watch } from '@stencil/core';
12
import type { IconAPI, IconStates, LabelPropType } from '../../schema';
23
import { validateLabel, watchString } from '../../schema';
3-
import { Component, h, Host, Prop, State, Watch } from '@stencil/core';
44

55
import type { JSX } from '@stencil/core';
66
import clsx from 'clsx';
7+
import { BEM_CLASS_ICON, BEM_CLASS_ICON__ICON } from './bem';
8+
79
/**
810
* @part icon - Ermöglicht das Styling des inneren Icons.
911
*/
@@ -18,7 +20,7 @@ export class KolIcon implements IconAPI {
1820
public render(): JSX.Element {
1921
const ariaShow = this.state._label.length > 0;
2022
return (
21-
<Host exportparts="icon" class="kol-icon">
23+
<Host exportparts="icon" class={BEM_CLASS_ICON}>
2224
<i
2325
aria-hidden={ariaShow ? undefined : 'true'}
2426
/**
@@ -28,7 +30,7 @@ export class KolIcon implements IconAPI {
2830
* Referenz: https://www.w3.org/TR/wai-aria/states_and_properties#aria-hidden
2931
*/
3032
aria-label={ariaShow ? this.state._label : undefined}
31-
class={clsx('kol-icon__icon', this.state._icons)}
33+
class={clsx(BEM_CLASS_ICON__ICON, this.state._icons)}
3234
part="icon"
3335
role="img"
3436
></i>

packages/components/src/functional-components/Alert/Alert.tsx

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { type FunctionalComponent as FC, h } from '@stencil/core';
2-
import type { JSXBase } from '@stencil/core/internal';
2+
import { type JSXBase } from '@stencil/core/internal';
33
import clsx from 'clsx';
44

5-
import { type InternalAlertProps } from '../../schema';
6-
import { translate } from '../../i18n';
75
import { KolButtonWcTag } from '../../core/component-names';
8-
6+
import { translate } from '../../i18n';
7+
import { type InternalAlertProps } from '../../schema';
98
import AlertIcon from '../AlertIcon';
109
import KolHeadingFc from '../Heading';
10+
import { genBemAlert as bem, BEM_CLASS_ALERT__CLOSER, BEM_CLASS_ALERT__CONTENT } from './bem';
1111

1212
export type KolAlertFcProps = JSXBase.HTMLAttributes<HTMLDivElement> &
1313
Partial<Omit<InternalAlertProps, 'on'>> & {
@@ -16,7 +16,18 @@ export type KolAlertFcProps = JSXBase.HTMLAttributes<HTMLDivElement> &
1616
};
1717

1818
const KolAlertFc: FC<KolAlertFcProps> = (props, children) => {
19-
const { class: classNames = {}, type = 'default', variant = 'msg', label, hasCloser, alert, onAlertTimeout, onCloserClick, level, ...other } = props;
19+
const {
20+
class: classNames = {},
21+
alert = false,
22+
hasCloser = false,
23+
label,
24+
level = 0,
25+
type = 'default',
26+
variant = 'msg',
27+
onAlertTimeout,
28+
onCloserClick,
29+
...other
30+
} = props;
2031

2132
if (alert) {
2233
/**
@@ -34,9 +45,21 @@ const KolAlertFc: FC<KolAlertFcProps> = (props, children) => {
3445
}, 10000);
3546
}
3647

48+
/**
49+
* Define the dynamic BEM class names for the alert component.
50+
*/
51+
const BEM_CLASS_ROOT = bem('kol-alert', {
52+
hasCloser: !!hasCloser,
53+
[`type-${type}`]: true,
54+
[`variant-${variant}`]: true,
55+
});
56+
const BEM_CLASS__HEADING = bem('kol-alert', 'heading', {
57+
[`h${level}`]: true,
58+
});
59+
3760
const rootProps: Partial<JSXBase.HTMLAttributes<HTMLDivElement>> = {
38-
class: clsx('kol-alert', `kol-alert--${type}`, `kol-alert--${variant}`, { 'kol-alert--hasCloser': !!hasCloser }, classNames),
39-
role: alert ? 'alert' : undefined,
61+
class: clsx(classNames, BEM_CLASS_ROOT),
62+
role: alert ? (type === 'error' ? 'alert' : 'status') : undefined,
4063
...other,
4164
};
4265

@@ -45,16 +68,20 @@ const KolAlertFc: FC<KolAlertFcProps> = (props, children) => {
4568
<div class="kol-alert__container">
4669
<AlertIcon label={label} type={type} />
4770
<div class="kol-alert__container-content">
48-
{label ? (
49-
<KolHeadingFc class="kol-alert__heading" level={level}>
71+
{label && (
72+
<KolHeadingFc class={BEM_CLASS__HEADING} level={level} id="heading">
5073
{label}
5174
</KolHeadingFc>
52-
) : null}
53-
{variant === 'msg' && <div class="kol-alert__content">{children}</div>}
75+
)}
76+
{variant === 'msg' && (
77+
<span class={BEM_CLASS_ALERT__CONTENT} aria-describedby={label ? 'heading' : undefined}>
78+
{children}
79+
</span>
80+
)}
5481
</div>
5582
{hasCloser && (
5683
<KolButtonWcTag
57-
class="kol-alert__close-button close"
84+
class={BEM_CLASS_ALERT__CLOSER}
5885
data-testid="alert-close-button"
5986
_ariaDescription={label?.trim() || ''}
6087
_hideLabel
@@ -66,10 +93,14 @@ const KolAlertFc: FC<KolAlertFcProps> = (props, children) => {
6693
_label={translate('kol-close-alert')}
6794
_on={{ onClick: onCloserClick }}
6895
_tooltipAlign="left"
69-
></KolButtonWcTag>
96+
/>
7097
)}
7198
</div>
72-
{variant === 'card' && <div class="kol-alert__content">{children}</div>}
99+
{variant === 'card' && (
100+
<div class={BEM_CLASS_ALERT__CONTENT} aria-describedby={label ? 'heading' : undefined}>
101+
{children}
102+
</div>
103+
)}
73104
</div>
74105
);
75106
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { generateBemClassNames } from 'typed-bem';
2+
3+
/**
4+
* This file defines the BEM schema for the kol-alert component.
5+
*/
6+
7+
/**
8+
* The schema defines the structure of the BEM class names
9+
*/
10+
type SCHEMA = {
11+
'kol-alert': {
12+
/**
13+
* Here we define the elements we needed
14+
* to realize a minimal full featured alert
15+
* component (DOM).
16+
*/
17+
elements: {
18+
container: {
19+
modifiers: null;
20+
};
21+
icon: {
22+
modifiers: null;
23+
};
24+
heading: {
25+
/**
26+
* Some elements needs modifiers to
27+
* style them properly.
28+
*/
29+
modifiers: Set<'h0' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'>;
30+
};
31+
content: {
32+
modifiers: null;
33+
};
34+
closer: {
35+
modifiers: null;
36+
};
37+
};
38+
/**
39+
* Here we define the modifiers related to the
40+
* kol-alert component properties.
41+
*/
42+
modifiers: Set<'hasCloser' | 'type-default' | 'type-error' | 'type-info' | 'type-success' | 'type-warning' | 'variant-card' | 'variant-msg'>;
43+
};
44+
};
45+
46+
/**
47+
* Define the generator function for the BEM class names.
48+
*/
49+
const bem = generateBemClassNames<SCHEMA>();
50+
51+
/**
52+
* Define a constants object that contains the BEM schema for
53+
* reuse to generate SCSS files in themes.
54+
*/
55+
const BEM: SCHEMA = {
56+
'kol-alert': {
57+
elements: {
58+
closer: { modifiers: null },
59+
heading: { modifiers: new Set(['h0', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']) },
60+
icon: { modifiers: null },
61+
content: { modifiers: null },
62+
container: { modifiers: null },
63+
},
64+
modifiers: new Set(['hasCloser', 'type-default', 'type-error', 'type-info', 'type-success', 'type-warning', 'variant-card', 'variant-msg']),
65+
},
66+
};
67+
68+
/**
69+
* Define the static BEM class names for the alert component.
70+
*/
71+
const BEM_CLASS_ALERT__CLOSER = bem('kol-alert', 'closer');
72+
const BEM_CLASS_ALERT__CONTENT = bem('kol-alert', 'content');
73+
const BEM_CLASS_ALERT__ICON = bem('kol-alert', 'icon');
74+
75+
export { bem as genBemAlert, BEM as BEM_ALERT };
76+
export { BEM_CLASS_ALERT__CLOSER, BEM_CLASS_ALERT__CONTENT, BEM_CLASS_ALERT__ICON };

packages/components/src/functional-components/Alert/test/__snapshots__/snapshot.test.tsx.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`KolAlertFc should render 1`] = `
4-
<div class="kol-alert kol-alert--default kol-alert--msg">
4+
<div class="kol-alert kol-alert--type-default kol-alert--variant-msg">
55
<div class="kol-alert__container">
66
<span class="visually-hidden">
77
kol-message
88
</span>
9-
<kol-icon _icons="codicon codicon-comment" _label="" class="kol-alert__heading-icon"></kol-icon>
9+
<kol-icon _icons="codicon codicon-comment" _label="" class="kol-alert__icon"></kol-icon>
1010
<div class="kol-alert__container-content">
11-
<div class="kol-alert__content"></div>
11+
<span class="kol-alert__content"></span>
1212
</div>
1313
</div>
1414
</div>

packages/components/src/functional-components/AlertIcon/AlertIcon.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Fragment, type FunctionalComponent as FC, h } from '@stencil/core';
22
import { KolIconTag } from '../../core/component-names';
33
import type { AlertType } from '../../schema';
44
import { translate } from '../../i18n';
5+
import { BEM_CLASS_ALERT__ICON } from '../Alert/bem';
56

67
/**
78
* The icon uses a visually-hidden span instead of an aria-label because the Alert might be referenced as content for aria-describedby.
@@ -12,7 +13,7 @@ const Icon: FC<{ ariaLabel: string; icon: string; label?: string }> = ({ ariaLab
1213
return (
1314
<>
1415
<span class="visually-hidden">{ariaLabel}</span>
15-
<KolIconTag class="kol-alert__heading-icon" _label="" _icons={icon} />
16+
<KolIconTag class={BEM_CLASS_ALERT__ICON} _label="" _icons={icon} />
1617
</>
1718
);
1819
};

packages/components/src/functional-components/FormField/tests/__snapshots__/snapshot.test.tsx.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,16 @@ exports[`KolFormFieldFc should render with error message 1`] = `
8787
</span>
8888
</label>
8989
<div class="kol-form-field__input"></div>
90-
<div aria-hidden="true" class="kol-alert kol-alert--error kol-alert--msg kol-form-field__msg" description="Error message" id="test-id-msg">
90+
<div aria-hidden="true" class="kol-alert kol-alert--type-error kol-alert--variant-msg kol-form-field__msg" description="Error message" id="test-id-msg">
9191
<div class="kol-alert__container">
9292
<span class="visually-hidden">
9393
kol-error
9494
</span>
95-
<kol-icon _icons="codicon codicon-error" _label="" class="kol-alert__heading-icon"></kol-icon>
95+
<kol-icon _icons="codicon codicon-error" _label="" class="kol-alert__icon"></kol-icon>
9696
<div class="kol-alert__container-content">
97-
<div class="kol-alert__content">
97+
<span class="kol-alert__content">
9898
Error message
99-
</div>
99+
</span>
100100
</div>
101101
</div>
102102
</div>

packages/components/src/functional-components/FormFieldMsg/tests/__snapshots__/snapshort.test.tsx.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`FormFieldMsgFc should render with all props 1`] = `
4-
<div aria-hidden="true" class="custom-class kol-alert kol-alert--error kol-alert--msg kol-form-field__msg" description="This is an error message" id="test-id-msg" role="alert">
4+
<div aria-hidden="true" class="custom-class kol-alert kol-alert--type-error kol-alert--variant-msg kol-form-field__msg" description="This is an error message" id="test-id-msg" role="alert">
55
<div class="kol-alert__container">
66
<span class="visually-hidden">
77
kol-error
88
</span>
9-
<kol-icon _icons="codicon codicon-error" _label="" class="kol-alert__heading-icon"></kol-icon>
9+
<kol-icon _icons="codicon codicon-error" _label="" class="kol-alert__icon"></kol-icon>
1010
<div class="kol-alert__container-content">
11-
<div class="kol-alert__content">
11+
<span class="kol-alert__content">
1212
This is an error message
13-
</div>
13+
</span>
1414
</div>
1515
</div>
1616
</div>

0 commit comments

Comments
 (0)