diff --git a/src/elements/card/card-badge/readme.md b/src/elements/card/card-badge/readme.md index fc950206dc..02af81ea8d 100644 --- a/src/elements/card/card-badge/readme.md +++ b/src/elements/card/card-badge/readme.md @@ -1,6 +1,6 @@ The `sbb-card-badge` can contain some information like prices or discounts, -and can be used in [sbb-card](/docs/components-sbb-card-sbb-card--docs) or -[sbb-selection-expansion-panel](/docs/components-sbb-selection-expansion-panel--docs). +and can be used in [sbb-card](/docs/elements-sbb-card-sbb-card--docs) or +[sbb-selection-expansion-panel](/docs/elements-sbb-selection-expansion-panel--docs). To achieve the correct spacing between elements inside the card badge, we recommend to use `span`-elements. All content parts are presented with a predefined gap in between. diff --git a/src/elements/core/i18n/i18n.ts b/src/elements/core/i18n/i18n.ts index 5b3cadcf6b..4b7f47a35d 100644 --- a/src/elements/core/i18n/i18n.ts +++ b/src/elements/core/i18n/i18n.ts @@ -78,21 +78,11 @@ export const i18nWagonLabelNumber: Record = { it: `Numero`, }; -export const i18nClosedCompartmentLabel = (wagonNumber?: number): Record => { - if (wagonNumber) { - return { - de: `Geschlossener Wagen mit der Nummer ${wagonNumber}`, - en: `Closed train coach with the number ${wagonNumber}`, - fr: `Wagon de train fermé avec le numéro ${wagonNumber}`, - it: `Carrozza del treno chiuso con il numero ${wagonNumber}`, - }; - } - return { - de: 'Geschlossener Zugwaggon', - en: 'Closed train coach', - fr: 'Wagon de train fermé', - it: 'Carrozza del treno chiuso', - }; +export const i18nClosedCompartmentLabel: Record = { + de: 'Geschlossener Zugwaggon', + en: 'Closed train coach', + fr: 'Wagon de train fermé', + it: 'Carrozza del treno chiuso', }; export const i18nLocomotiveLabel: Record = { @@ -102,6 +92,27 @@ export const i18nLocomotiveLabel: Record = { it: 'Locomotiva', }; +export const i18nSleepingWagonLabel: Record = { + de: 'Schlafwagen', + en: 'Sleeping car', + fr: 'Voiture-lits', + it: 'Carrozza letti', +}; + +export const i18nCouchetteWagonLabel: Record = { + de: 'Liegewagen', + en: 'Couchette car', + fr: 'Voiture-couchettes', + it: 'Carrozza cuccette', +}; + +export const i18nRestaurantWagonLabel: Record = { + de: 'Speisewagen', + en: 'Dining car', + fr: 'Voiture-restaurant', + it: 'Carrozza ristorante', +}; + export const i18nBlockedPassage: Record> = { previous: { de: 'Kein Durchgang zum vorherigen Wagen', diff --git a/src/elements/core/styles/core.scss b/src/elements/core/styles/core.scss index 38ddec81fe..89284eb0ec 100644 --- a/src/elements/core/styles/core.scss +++ b/src/elements/core/styles/core.scss @@ -153,3 +153,36 @@ input[data-sbb-time-input] { max-width: var(--sbb-time-input-s-max-width); } } + +// TODO: move to train formation after css refactoring +sbb-train-formation:has(sbb-train[direction-label]) { + --sbb-train-formation-reserve-spacing-display: block; +} + +// TODO: move to train formation after css refactoring +sbb-train-formation:has(sbb-train-wagon[sector]) { + --sbb-train-formation-show-sectors-gap: 1; +} + +// TODO: move to train formation after css refactoring +sbb-train-formation:not(:has(sbb-train-wagon[label])) { + --sbb-train-formation-wagon-label-display: none; +} + +// TODO: Move to sbb-train-wagon after CSS refactoring +sbb-train-formation[view='side'] sbb-train-wagon { + --sbb-train-wagon-wagon-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40' fill='none'%3E%3Cpath d='M12.5,0.5 h55 a12,12 0 0 1 12,12 v15 a12,12 0 0 1 -12,12 h-55 a12,12 0 0 1 -12,-12 v-15 a12,12 0 0 1 12,-12 z' stroke='%23000000' stroke-width='1'/%3E%3C/svg%3E"); + --sbb-train-wagon-wagon-closed-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40' fill='none'%3E%3Crect x='0.5' y='0.5' width='79' height='39' rx='11.5' stroke='%23000000'/%3E%3Cpath d='M76 4L4 36' stroke='%23000000'/%3E%3Cpath d='M76 36L4 4' stroke='%23000000'/%3E%3C/svg%3E"); + --sbb-train-wagon-wagon-end-left-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40' fill='none'%3E%3Cpath d='M10.745 7.90416C13.5624 3.30431 18.5686 0.5 23.9627 0.5H68C74.3513 0.5 79.5 5.64873 79.5 12V28C79.5 34.3513 74.3513 39.5 68 39.5H11.922C2.93614 39.5 -2.57807 29.6562 2.11537 21.9934L10.745 7.90416Z' stroke='%23000000'/%3E%3C/svg%3E"); + --sbb-train-wagon-locomotive-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40' fill='none'%3E%3Cpath d='M10.745 7.90416C13.5624 3.30431 18.5686 0.5 23.9627 0.5H56.0373C61.4314 0.5 66.4376 3.30432 69.255 7.90416L77.8846 21.9934C82.5781 29.6562 77.0639 39.5 68.078 39.5H11.922C2.93615 39.5 -2.57807 29.6562 2.11537 21.9934L10.745 7.90416Z' stroke='%23000000'/%3E%3C/svg%3E"); + --sbb-train-wagon-wagon-end-right-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40' fill='none'%3E%3Cpath d='M0.5 12C0.5 5.64873 5.64873 0.5 12 0.5H56.0373C61.4314 0.5 66.4376 3.30432 69.255 7.90416L77.8846 21.9934C82.5781 29.6562 77.0638 39.5 68.0779 39.5H12C5.64873 39.5 0.5 34.3513 0.5 28V12Z' stroke='%23000000'/%3E%3C/svg%3E"); +} + +// TODO: Move to sbb-train-wagon after CSS refactoring +sbb-train-formation[view='top'] sbb-train-wagon { + --sbb-train-wagon-wagon-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40' fill='none'%3E%3Cpath d='M8.5,0.5 h63 a8,8 0 0 1 8,8 v23 a8,8 0 0 1 -8,8 h-63 a8,8 0 0 1 -8,-8 v-23 a8,8 0 0 1 8,-8 z' stroke='%23000000' stroke-width='1'/%3E%3C/svg%3E%0A"); + --sbb-train-wagon-wagon-closed-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40' fill='none'%3E%3Crect x='0.5' y='0.5' width='79' height='39' rx='7.5' stroke='%23000000'/%3E%3Cpath d='M77.5 2.5L2.5 37.5' stroke='%23000000'/%3E%3Cpath d='M77.5 37.5L2.5 2.5' stroke='%23000000'/%3E%3C/svg%3E"); + --sbb-train-wagon-wagon-end-left-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40' fill='none'%3E%3Cpath transform='translate(80,40) rotate(180)' d='M8.5,0.5 h51.5 a19.5,19.5 0 0 1 19.5,19.5 v0 a19.5,19.5 0 0 1 -19.5,19.5 h-51.5 a8,8 0 0 1 -8,-8 v-23 a8,8 0 0 1 8,-8 z' stroke='%23000000' stroke-width='1'/%3E%3C/svg%3E"); + --sbb-train-wagon-locomotive-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40' fill='none'%3E%3Cpath d='M20,0.5 h40 a19.5,19.5 0 0 1 19.5,19.5 v0 a19.5,19.5 0 0 1 -19.5,19.5 h-40 a19.5,19.5 0 0 1 -19.5,-19.5 v0 a19.5,19.5 0 0 1 19.5,-19.5 z' stroke='%23000000' stroke-width='1'/%3E%3C/svg%3E%0A"); + --sbb-train-wagon-wagon-end-right-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40' fill='none'%3E%3Cpath d='M8.5,0.5 h51.5 a19.5,19.5 0 0 1 19.5,19.5 v0 a19.5,19.5 0 0 1 -19.5,19.5 h-51.5 a8,8 0 0 1 -8,-8 v-23 a8,8 0 0 1 8,-8 z' stroke='%23000000' stroke-width='1'/%3E%3C/svg%3E"); +} diff --git a/src/elements/train/train-blocked-passage/readme.md b/src/elements/train/train-blocked-passage/readme.md index 5ad00ed80a..c911041f8b 100644 --- a/src/elements/train/train-blocked-passage/readme.md +++ b/src/elements/train/train-blocked-passage/readme.md @@ -1,7 +1,7 @@ A `sbb-train-blocked-passage` is a visual representation of a blocked passage between -[sbb-train-wagon](/docs/timetable-sbb-train-wagon--docs)s. +[sbb-train-wagon](/docs/elements-timetable-sbb-train-wagon--docs)s. -It is used inside the [sbb-train](/docs/timetable-sbb-train--docs) element. +It is used inside the [sbb-train](/docs/elements-timetable-sbb-train--docs) element. ```html diff --git a/src/elements/train/train-blocked-passage/train-blocked-passage.scss b/src/elements/train/train-blocked-passage/train-blocked-passage.scss index ae39baa145..01ed779a5c 100644 --- a/src/elements/train/train-blocked-passage/train-blocked-passage.scss +++ b/src/elements/train/train-blocked-passage/train-blocked-passage.scss @@ -7,7 +7,7 @@ display: block; --sbb-train-blocked-passage-height: var(--sbb-train-formation-wagon-height); - --sbb-train-blocked-passage-background-color: var(--sbb-color-red); + --sbb-train-blocked-passage-background-color: var(--sbb-color-red125); --sbb-train-blocked-passage-bar-color: var(--sbb-color-white); --sbb-train-blocked-passage-icon-dimension: #{sbb.px-to-rem-build(16)}; --sbb-train-blocked-passage-icon-bar-width: #{sbb.px-to-rem-build(10)}; @@ -24,7 +24,7 @@ &::before { content: ''; display: var(--sbb-train-formation-wagon-label-display, block); - height: calc(var(--sbb-font-size-text-s) * var(--sbb-typo-line-height-body-text)); + height: calc(var(--sbb-font-size-text-xxs) * var(--sbb-typo-line-height-body-text)); } } diff --git a/src/elements/train/train-formation/__snapshots__/train-formation.snapshot.spec.snap.js b/src/elements/train/train-formation/__snapshots__/train-formation.snapshot.spec.snap.js index 49861475ab..79a31b8aba 100644 --- a/src/elements/train/train-formation/__snapshots__/train-formation.snapshot.spec.snap.js +++ b/src/elements/train/train-formation/__snapshots__/train-formation.snapshot.spec.snap.js @@ -2,13 +2,12 @@ export const snapshots = {}; snapshots["sbb-train-formation should render with one train DOM"] = -` +` +`
`; @@ -194,7 +167,7 @@ snapshots["sbb-train-wagon should render as type locomotive DOM"] = snapshots["sbb-train-wagon should render as type locomotive Shadow DOM"] = `
- +
Locomotive @@ -203,10 +176,14 @@ snapshots["sbb-train-wagon should render as type locomotive Shadow DOM"] = class="sbb-train-wagon__label" > - +
, Top of the train +
`; /* end snapshot sbb-train-wagon should render as type locomotive Shadow DOM */ @@ -219,7 +196,7 @@ snapshots["sbb-train-wagon should render as type closed wagon without number DOM snapshots["sbb-train-wagon should render as type closed wagon without number Shadow DOM"] = `
- +
Closed train coach @@ -228,6 +205,10 @@ snapshots["sbb-train-wagon should render as type closed wagon without number Sha class="sbb-train-wagon__label" > +
+
`; @@ -241,31 +222,104 @@ snapshots["sbb-train-wagon should render as type wagon A11y tree Chrome"] = "children": [ { "role": "text", - "name": "Number," + "name": "Number, 38" }, { "role": "text", - "name": " " + "name": "First Class" }, { "role": "text", - "name": "38" + "name": "No passage to the previous train coach" + } + ] +} +

+`; +/* end snapshot sbb-train-wagon should render as type wagon A11y tree Chrome */ + +snapshots["sbb-train-wagon should render as type wagon A11y tree Firefox"] = +`

+ { + "role": "document", + "name": "", + "children": [ + { + "role": "text leaf", + "name": "Number, 38" }, { - "role": "text", + "role": "text leaf", "name": "First Class" }, { - "role": "text", + "role": "text leaf", "name": "No passage to the previous train coach" } ] }

`; -/* end snapshot sbb-train-wagon should render as type wagon A11y tree Chrome */ +/* end snapshot sbb-train-wagon should render as type wagon A11y tree Firefox */ -snapshots["sbb-train-wagon should render as type wagon A11y tree Firefox"] = +snapshots["sbb-train-wagon should render as type wagon end with only one property DOM"] = +` + +`; +/* end snapshot sbb-train-wagon should render as type wagon end with only one property DOM */ + +snapshots["sbb-train-wagon should render as type wagon end with only one property Shadow DOM"] = +`
+
+ + Train coach + + + + + First Class + + + +
+ +
+`; +/* end snapshot sbb-train-wagon should render as type wagon end with only one property Shadow DOM */ + +snapshots["sbb-train-wagon should render as type wagon end with only one property A11y tree Chrome"] = +`

+ { + "role": "WebArea", + "name": "", + "children": [ + { + "role": "text", + "name": "Train coach" + }, + { + "role": "text", + "name": "First Class" + } + ] +} +

+`; +/* end snapshot sbb-train-wagon should render as type wagon end with only one property A11y tree Chrome */ + +snapshots["sbb-train-wagon should render as type wagon end with only one property A11y tree Firefox"] = `

{ "role": "document", @@ -273,16 +327,232 @@ snapshots["sbb-train-wagon should render as type wagon A11y tree Firefox"] = "children": [ { "role": "text leaf", - "name": "Number," + "name": "Train coach" }, { "role": "text leaf", - "name": "38" + "name": "First Class" + } + ] +} +

+`; +/* end snapshot sbb-train-wagon should render as type wagon end with only one property A11y tree Firefox */ + +snapshots["sbb-train-wagon should render as type wagon-end-right with only one property DOM"] = +` + +`; +/* end snapshot sbb-train-wagon should render as type wagon-end-right with only one property DOM */ + +snapshots["sbb-train-wagon should render as type wagon-end-right with only one property Shadow DOM"] = +`
+
    + +
  • + + First Class + + +
  • +
  • + No passage to the next train coach +
  • +
+ +
+`; +/* end snapshot sbb-train-wagon should render as type wagon-end-right with only one property Shadow DOM */ + +snapshots["sbb-train-wagon should render as type wagon-end-right with only one property A11y tree Chrome"] = +`

+ { + "role": "WebArea", + "name": "", + "children": [ + { + "role": "text", + "name": "First Class" }, + { + "role": "text", + "name": "No passage to the next train coach" + } + ] +} +

+`; +/* end snapshot sbb-train-wagon should render as type wagon-end-right with only one property A11y tree Chrome */ + +snapshots["sbb-train-wagon should render as type wagon-end-right with only one property A11y tree Firefox"] = +`

+ { + "role": "document", + "name": "", + "children": [ { "role": "text leaf", "name": "First Class" }, + { + "role": "text leaf", + "name": "No passage to the next train coach" + } + ] +} +

+`; +/* end snapshot sbb-train-wagon should render as type wagon-end-right with only one property A11y tree Firefox */ + +snapshots["sbb-train-wagon should render with only label DOM"] = +` + +`; +/* end snapshot sbb-train-wagon should render with only label DOM */ + +snapshots["sbb-train-wagon should render with only label Shadow DOM"] = +`
+
+ + Train coach + + + + Number, 1 + + + +
+ +
+`; +/* end snapshot sbb-train-wagon should render with only label Shadow DOM */ + +snapshots["sbb-train-wagon should render with only label A11y tree Firefox"] = +`

+ { + "role": "document", + "name": "", + "children": [ + { + "role": "text leaf", + "name": "Train coach" + }, + { + "role": "text leaf", + "name": "Number, 1" + } + ] +} +

+`; +/* end snapshot sbb-train-wagon should render with only label A11y tree Firefox */ + +snapshots["sbb-train-wagon should render with only label A11y tree Chrome"] = +`

+ { + "role": "WebArea", + "name": "", + "children": [ + { + "role": "text", + "name": "Train coach" + }, + { + "role": "text", + "name": "Number, 1" + } + ] +} +

+`; +/* end snapshot sbb-train-wagon should render with only label A11y tree Chrome */ + +snapshots["sbb-train-wagon should render as type wagon-end-left DOM"] = +` + +`; +/* end snapshot sbb-train-wagon should render as type wagon-end-left DOM */ + +snapshots["sbb-train-wagon should render as type wagon-end-left Shadow DOM"] = +`
+
+ + Train coach + + + + No passage to the previous train coach + +
+ +
+`; +/* end snapshot sbb-train-wagon should render as type wagon-end-left Shadow DOM */ + +snapshots["sbb-train-wagon should render as type wagon-end-left A11y tree Chrome"] = +`

+ { + "role": "WebArea", + "name": "", + "children": [ + { + "role": "text", + "name": "Train coach" + }, + { + "role": "text", + "name": "No passage to the previous train coach" + } + ] +} +

+`; +/* end snapshot sbb-train-wagon should render as type wagon-end-left A11y tree Chrome */ + +snapshots["sbb-train-wagon should render as type wagon-end-left A11y tree Firefox"] = +`

+ { + "role": "document", + "name": "", + "children": [ + { + "role": "text leaf", + "name": "Train coach" + }, { "role": "text leaf", "name": "No passage to the previous train coach" @@ -291,5 +561,5 @@ snapshots["sbb-train-wagon should render as type wagon A11y tree Firefox"] = }

`; -/* end snapshot sbb-train-wagon should render as type wagon A11y tree Firefox */ +/* end snapshot sbb-train-wagon should render as type wagon-end-left A11y tree Firefox */ diff --git a/src/elements/train/train-wagon/readme.md b/src/elements/train/train-wagon/readme.md index b328d0411d..da460c539a 100644 --- a/src/elements/train/train-wagon/readme.md +++ b/src/elements/train/train-wagon/readme.md @@ -1,30 +1,33 @@ THe `sbb-train-wagon` is a component which represents a train compartment. -It is used inside the [sbb-train](/docs/timetable-sbb-train--docs) element. +It is used inside the [sbb-train](/docs/elements-timetable-sbb-train--docs) element. ## Variants -The component can visualize a locomotive or a wagon (possibly closed), -so it has three different variants, based on the value of the `type` property. +With the `type` property, the component can visualize different types of wagons and locomotives. +For the types `wagon-end-left` and `wagon-end-right` the blocked passage information is set automatically. ```html - + + + + + - ``` -The property `occupancy` sets the component's inner icon; available values are `high`, `medium`, `low` and `none`; +The property `occupancy` sets the component's inner icon; available values are `high`, `medium`, `low`, `none` and `null`; it's also possible to display the wagon class at component's end using the `wagonClass` property -and a `label` above the component with the self-named property. +and a wagon number (property `label`) above the component. ```html ``` **Note:** -A `sbb-train-wagon` with `type="wagon"` has the possibilities of slotting icons. +A `sbb-train-wagon` with `type="wagon"` has the possibilities of slotting attribute icons. They will be applied internally into a list (using `
    ` and `
  • `) and requires an `aria-label` for each slotted icon. ```html @@ -43,15 +46,15 @@ They will be applied internally into a list (using `
      ` and `
    • `) and requir ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| ----------------------------- | ------------------------------- | ------- | ------------------------------------------ | --------- | -------------------------------------------------------------------------- | -| `additionalAccessibilityText` | `additional-accessibility-text` | public | `string` | `''` | Additional accessibility text which will be appended to the end. | -| `blockedPassage` | `blocked-passage` | public | `'previous' \| 'next' \| 'both' \| 'none'` | `'none'` | Accessibility text for blocked passages of the wagon. | -| `label` | `label` | public | `string` | `''` | Visible label for the wagon number. Not used by type locomotive or closed. | -| `occupancy` | `occupancy` | public | `SbbOccupancy` | `'none'` | Occupancy of a wagon. | -| `sector` | `sector` | public | `string` | `''` | Sector in which to wagon stops. | -| `type` | `type` | public | `'locomotive' \| 'closed' \| 'wagon'` | `'wagon'` | Wagon type. | -| `wagonClass` | `wagon-class` | public | `'1' \| '2' \| null` | `null` | Visible class label of a wagon. | +| Name | Attribute | Privacy | Type | Default | Description | +| ----------------------------- | ------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `additionalAccessibilityText` | `additional-accessibility-text` | public | `string` | `''` | Additional accessibility text which will be appended to the end. | +| `blockedPassage` | `blocked-passage` | public | `'previous' \| 'next' \| 'both' \| 'none'` | `'none'` | Accessibility text for blocked passages of the wagon. | +| `label` | `label` | public | `string` | `''` | Wagon number | +| `occupancy` | `occupancy` | public | `SbbOccupancy \| null` | `null` | Occupancy of a wagon. | +| `sector` | `sector` | public | `string` | `''` | Sector in which the wagon stops. | +| `type` | `type` | public | `'wagon' \| 'wagon-end-left' \| 'wagon-end-right' \| 'couchette' \| 'sleeping' \| 'restaurant' \| 'locomotive' \| 'closed'` | `'wagon'` | Wagon type. For `wagon-end-left` and `wagon-end-right`, please set the corresponding value of the `blockedPassage` property. | +| `wagonClass` | `wagon-class` | public | `'1' \| '2' \| null` | `null` | Class label | ## Slots diff --git a/src/elements/train/train-wagon/train-wagon.scss b/src/elements/train/train-wagon/train-wagon.scss index cac5285574..0a8e648b3c 100644 --- a/src/elements/train/train-wagon/train-wagon.scss +++ b/src/elements/train/train-wagon/train-wagon.scss @@ -8,26 +8,63 @@ --sbb-train-wagon-width: var(--sbb-train-formation-wagon-width); --sbb-train-wagon-height: var(--sbb-train-formation-wagon-height); - --sbb-train-wagon-inner-padding: var(--sbb-spacing-fixed-3x); - --sbb-train-wagon-shape-border-radius: var(--sbb-train-formation-wagon-border-radius); --sbb-train-wagon-shape-color: var(--sbb-color-charcoal); - --sbb-train-wagon-shape-color-closed: var(--sbb-color-metal); - --sbb-train-wagon-shape-width: var(--sbb-border-width-1x); - --sbb-train-wagon-gap: var(--sbb-spacing-fixed-2x); - --sbb-train-wagon-icons-gap: var(--sbb-spacing-fixed-1x); - --sbb-train-wagon-icons-height: #{sbb.px-to-rem-build(14)}; + --sbb-train-wagon-main-icon-height: #{sbb.px-to-rem-build(20)}; + --sbb-train-wagon-main-icon-margin-block-start: #{sbb.px-to-rem-build(10)}; + --sbb-train-wagon-attribute-icon-gap: var(--sbb-spacing-fixed-1x); + --sbb-train-wagon-attribute-icon-height: #{sbb.px-to-rem-build(14)}; + --sbb-train-wagon-attribute-icon-color: var(--sbb-color-granite); + --sbb-train-wagon-occupancy-height: #{sbb.px-to-rem-build(12)}; @include sbb.if-forced-colors { - --sbb-train-wagon-shape-color-closed: CanvasText; + --sbb-train-wagon-shape-color: CanvasText; } } +:host(:is([type='closed'], [type='locomotive'])) { + --sbb-train-wagon-shape-color: var(--sbb-color-metal); + + @include sbb.if-forced-colors { + --sbb-train-wagon-shape-color: CanvasText; + } +} + +:host([type='closed']) { + @include sbb.if-forced-colors { + --sbb-train-wagon-shape-color: GrayText; + } +} + +:host(:is([type='sleeping'], [type='couchette'])) { + --sbb-train-wagon-main-icon-margin-block-start: #{sbb.px-to-rem-build(6.25)}; +} + +:host(:is([type='wagon'], [type='restaurant'], [type='sleeping'], [type='couchette'])) { + --sbb-train-wagon-shape: var(--sbb-train-wagon-wagon-shape); +} + +:host([type='closed']) { + --sbb-train-wagon-shape: var(--sbb-train-wagon-wagon-closed-shape); +} + +:host([type='locomotive']) { + --sbb-train-wagon-shape: var(--sbb-train-wagon-locomotive-shape); +} + +:host([type='wagon-end-left']) { + --sbb-train-wagon-shape: var(--sbb-train-wagon-wagon-end-left-shape); +} + +:host([type='wagon-end-right']) { + --sbb-train-wagon-shape: var(--sbb-train-wagon-wagon-end-right-shape); +} + .sbb-train-wagon { display: flex; flex-direction: column; align-items: center; width: var(--sbb-train-wagon-width); - gap: var(--sbb-train-wagon-gap); + gap: var(--sbb-train-formation-vertical-gap); } .sbb-train-wagon__compartment { @@ -37,48 +74,33 @@ grid-template: 'label label' auto 'occupancy class' 1fr / 1fr 1fr; - position: relative; - width: 100%; - // Reserve space because there is no content in the closed wagon + // Type shape &::before { - :host([type='closed']) &, - :host(:not([data-has-visible-wagon-content])) & { - content: ''; - grid-area: occupancy / span 2; - height: var(--sbb-train-wagon-height); - } - } - - &::after { content: ''; - display: block; - position: absolute; - inset-inline: 0; - inset-block: auto 0; + grid-column: occupancy / class; + grid-row: occupancy; + pointer-events: none; width: var(--sbb-train-wagon-width); height: var(--sbb-train-wagon-height); - border-radius: var(--sbb-train-wagon-shape-border-radius); - - :host([type='wagon']) & { - border: var(--sbb-train-wagon-shape-width) solid var(--sbb-train-wagon-shape-color); - } + background-color: var(--sbb-train-wagon-shape-color); - :host([type='closed']) & { - border: var(--sbb-train-wagon-shape-width) solid var(--sbb-train-wagon-shape-color-closed); - } + // Using mask-image takes the background-color as stroke color + mask-image: var(--sbb-train-wagon-shape); } } -.sbb-train-wagon__occupancy, -.sbb-train-wagon__class { - height: var(--sbb-train-wagon-height); - display: flex; - align-items: center; +.sbb-train-wagon__main-icon { + --sbb-icon-svg-height: var(--sbb-train-wagon-main-icon-height); + + grid-column: occupancy / class; + grid-row: occupancy; + margin-block-start: var(--sbb-train-wagon-main-icon-margin-block-start); + justify-self: center; } .sbb-train-wagon__label { - @include sbb.text-s--regular; + @include sbb.text-xxs--regular; grid-area: label; text-align: center; @@ -86,46 +108,41 @@ min-height: calc(1em * var(--sbb-typo-line-height-body-text)); } +.sbb-train-wagon__occupancy, +.sbb-train-wagon__class { + height: var(--sbb-train-wagon-height); + display: flex; + align-items: center; + justify-content: center; +} + .sbb-train-wagon__occupancy { + --sbb-icon-svg-height: var(--sbb-train-wagon-occupancy-height); + grid-area: occupancy; - padding-inline-start: var(--sbb-train-wagon-inner-padding); } .sbb-train-wagon__class { @include sbb.title-6($exclude-spacing: true); grid-area: class; - padding-inline-end: var(--sbb-train-wagon-inner-padding); - margin-inline-start: auto; -} - -.sbb-train-wagon__locomotive { - grid-area: occupancy / span 2; -} - -.sbb-train-wagon__icons { - --sbb-icon-svg-height: var(--sbb-train-wagon-icons-height); - - display: flex; - - &[hidden] { - // Don't reserve any space in hidden case - position: absolute; - } } -.sbb-train-wagon__icons-list { +.sbb-train-wagon__attribute-icon-list { @include sbb.list-reset; + --sbb-icon-svg-height: var(--sbb-train-wagon-attribute-icon-height); + display: flex; flex-wrap: wrap; - gap: var(--sbb-train-wagon-icons-gap); justify-content: center; + gap: var(--sbb-train-wagon-attribute-icon-gap); + color: var(--sbb-train-wagon-attribute-icon-color); } // Using ... li selector, because the li generation // is handled in the component base class. -.sbb-train-wagon__icons-list > :is(li, span) { +.sbb-train-wagon__attribute-icon-list > :is(li, span) { display: inline-flex; } diff --git a/src/elements/train/train-wagon/train-wagon.snapshot.spec.ts b/src/elements/train/train-wagon/train-wagon.snapshot.spec.ts index 1d36445d5c..7c4cdd334f 100644 --- a/src/elements/train/train-wagon/train-wagon.snapshot.spec.ts +++ b/src/elements/train/train-wagon/train-wagon.snapshot.spec.ts @@ -36,6 +36,56 @@ describe(`sbb-train-wagon`, () => { testA11yTreeSnapshot(); }); + describe('should render as type wagon-end-right with only one property', async () => { + beforeEach(async () => { + element = await fixture( + html``, + ); + }); + + it('DOM', async () => { + await expect(element).dom.to.be.equalSnapshot(); + }); + + it('Shadow DOM', async () => { + await expect(element).shadowDom.to.be.equalSnapshot(); + }); + + testA11yTreeSnapshot(); + }); + + describe('should render as type wagon-end-left', async () => { + beforeEach(async () => { + element = await fixture(html``); + }); + + it('DOM', async () => { + await expect(element).dom.to.be.equalSnapshot(); + }); + + it('Shadow DOM', async () => { + await expect(element).shadowDom.to.be.equalSnapshot(); + }); + + testA11yTreeSnapshot(); + }); + + describe('should render with only label', async () => { + beforeEach(async () => { + element = await fixture(html``); + }); + + it('DOM', async () => { + await expect(element).dom.to.be.equalSnapshot(); + }); + + it('Shadow DOM', async () => { + await expect(element).shadowDom.to.be.equalSnapshot(); + }); + + testA11yTreeSnapshot(); + }); + describe('should render as type wagon with one icon', async () => { beforeEach(async () => { element = await fixture( diff --git a/src/elements/train/train-wagon/train-wagon.spec.ts b/src/elements/train/train-wagon/train-wagon.spec.ts index 74c5ca9afc..c6d1912032 100644 --- a/src/elements/train/train-wagon/train-wagon.spec.ts +++ b/src/elements/train/train-wagon/train-wagon.spec.ts @@ -1,65 +1,75 @@ import { assert, expect } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; +import { setOrRemoveAttribute } from '../../core/dom.js'; import { fixture } from '../../core/testing/private.js'; import { EventSpy, waitForLitRender } from '../../core/testing.js'; import type { SbbIconElement } from '../../icon.js'; import { SbbTrainWagonElement } from './train-wagon.js'; -async function extractAriaLabels( - properties: Partial< - Pick< - SbbTrainWagonElement, - | 'type' - | 'occupancy' - | 'sector' - | 'blockedPassage' - | 'wagonClass' - | 'label' - | 'additionalAccessibilityText' - > - >, -): Promise { - const element = await fixture(html``); - - // attributes - ( - [ - 'type', - 'occupancy', - 'sector', - 'blockedPassage', - 'wagonClass', - 'label', - 'additionalAccessibilityText', - ] as const - ).forEach((attr) => { - const attributeValue = properties[attr]; - // Convert camelCase to kebab-case - const attributeName = attr.replace( - /[A-Z]+(?![a-z])|[A-Z]/g, - ($, ofs) => (ofs ? '-' : '') + $.toLowerCase(), - ); - element.setAttribute(attributeName, attributeValue ?? ''); - }); +describe(`sbb-train-wagon`, () => { + let element: SbbTrainWagonElement; - await waitForLitRender(element); + async function extractAriaLabels( + properties: Partial< + Pick< + SbbTrainWagonElement, + | 'type' + | 'occupancy' + | 'sector' + | 'blockedPassage' + | 'wagonClass' + | 'label' + | 'additionalAccessibilityText' + > + >, + ): Promise { + // attributes + ( + [ + 'type', + 'occupancy', + 'sector', + 'blockedPassage', + 'wagonClass', + 'label', + 'additionalAccessibilityText', + ] as const + ).forEach((attr) => { + const defaultValueMap = new Map(); + + defaultValueMap.set('type', 'wagon'); + defaultValueMap.set('occupancy', null); + defaultValueMap.set('sector', ''); + defaultValueMap.set('blockedPassage', 'none'); + defaultValueMap.set('wagonClass', null); + defaultValueMap.set('label', ''); + defaultValueMap.set('additionalAccessibilityText', ''); + + const attributeValue = properties[attr]; + // Convert camelCase to kebab-case + const attributeName = attr.replace( + /[A-Z]+(?![a-z])|[A-Z]/g, + ($, ofs) => (ofs ? '-' : '') + $.toLowerCase(), + ); + setOrRemoveAttribute(element, attributeName, attributeValue ?? defaultValueMap.get(attr)); + }); - // Select all accessibility relevant text parts - return Array.from( - element.shadowRoot!.querySelectorAll( - '[aria-hidden=false], [aria-label]:not(.sbb-train-wagon__icons-list), .sbb-screen-reader-only:not(.sbb-train-wagon__label > span)', - ), - ).map((entry) => - entry.hasAttribute('aria-label') - ? entry.getAttribute('aria-label')! - : entry.textContent!.replace(/\s+/g, ' ').trim(), - ); -} + await waitForLitRender(element); -describe(`sbb-train-wagon`, () => { - let element: SbbTrainWagonElement; + // Select all accessibility relevant text parts + // The alternative of a11ySnapshot() does not work as the list title can't be extracted reliable. + return Array.from( + element.shadowRoot!.querySelectorAll( + '[aria-label]:not(.sbb-train-wagon__attribute-icon-list), .sbb-screen-reader-only', + ), + ).map((entry) => + entry.hasAttribute('aria-label') + ? entry.getAttribute('aria-label')! + : entry.textContent!.replace(/\s+/g, ' ').trim(), + ); + } it('renders', async () => { element = await fixture(html``); @@ -97,22 +107,26 @@ describe(`sbb-train-wagon`, () => { }); it('should set aria labels correctly', async () => { - expect(await extractAriaLabels({})).to.be.eql([]); + element = await fixture(html``); + + expect(await extractAriaLabels({ type: 'wagon' })).to.be.eql(['Train coach']); expect(await extractAriaLabels({ type: 'locomotive' })).to.be.eql(['Locomotive']); + expect(await extractAriaLabels({ type: 'sleeping' })).to.be.eql(['Sleeping car']); + expect(await extractAriaLabels({ type: 'restaurant' })).to.be.eql(['Dining car']); + expect( await extractAriaLabels({ type: 'closed', additionalAccessibilityText: `Don't enter` }), ).to.be.eql(['Closed train coach', `, Don't enter`]); expect(await extractAriaLabels({ type: 'wagon' })).to.be.eql(['Train coach']); expect(await extractAriaLabels({ sector: 'A', type: 'locomotive' })).to.be.eql([ - 'Locomotive , Sector, A', + 'Locomotive, Sector, A', ]); expect(await extractAriaLabels({ sector: 'A', type: 'closed' })).to.be.eql([ - 'Closed train coach , Sector, A', + 'Closed train coach, Sector, A', ]); expect(await extractAriaLabels({ sector: 'A', type: 'wagon' })).to.be.eql([ - 'Train coach', - 'Sector, A', + 'Train coach, Sector, A', ]); expect( @@ -162,5 +176,36 @@ describe(`sbb-train-wagon`, () => { expect(await extractAriaLabels({ type: 'wagon', blockedPassage: 'none' })).to.be.eql([ 'Train coach', ]); + + expect(await extractAriaLabels({ type: 'wagon-end-left' })).to.be.eql([ + 'Train coach', + 'No passage to the previous train coach', + ]); + + expect(await extractAriaLabels({ type: 'wagon-end-right' })).to.be.eql([ + 'Train coach', + 'No passage to the next train coach', + ]); + + expect(await extractAriaLabels({ type: 'wagon-end-right', blockedPassage: 'both' })).to.be.eql([ + 'Train coach', + 'No passage to the next and previous train coach', + ]); + + expect(await extractAriaLabels({ type: 'wagon-end-left', sector: 'A' })).to.be.eql([ + 'Train coach', + 'Sector, A', + 'No passage to the previous train coach', + ]); + + expect(await extractAriaLabels({ type: 'wagon-end-right', sector: 'A' })).to.be.eql([ + 'Train coach', + 'Sector, A', + 'No passage to the next train coach', + ]); + + expect( + await extractAriaLabels({ type: 'wagon-end-right', blockedPassage: 'both', sector: 'A' }), + ).to.be.eql(['Train coach', 'Sector, A', 'No passage to the next and previous train coach']); }); }); diff --git a/src/elements/train/train-wagon/train-wagon.stories.ts b/src/elements/train/train-wagon/train-wagon.stories.ts index ab9d6dd422..124f1edd1d 100644 --- a/src/elements/train/train-wagon/train-wagon.stories.ts +++ b/src/elements/train/train-wagon/train-wagon.stories.ts @@ -7,31 +7,27 @@ import { sbbSpread } from '../../../storybook/helpers/spread.js'; import readme from './readme.md?raw'; import './train-wagon.js'; +import '../train-formation.js'; +import '../train.js'; + +const trainFormationWrapper = (content: TemplateResult): TemplateResult => + html`${content}`; const Template = (args: Args): TemplateResult => - html``; - -const WagonIconsTemplate = (args: Args): TemplateResult => html` - - - - - -`; - -const WagonOneIconTemplate = (args: Args): TemplateResult => html` - - - -`; + trainFormationWrapper(html``); + +const WagonIconsTemplate = (args: Args): TemplateResult => + trainFormationWrapper(html` + + + + + + `); const label: InputType = { control: { @@ -49,21 +45,30 @@ const occupancy: InputType = { control: { type: 'inline-radio', }, - options: ['high', 'medium', 'low', 'none'], + options: ['high', 'medium', 'low', 'none', null], }; const type: InputType = { control: { type: 'inline-radio', }, - options: ['locomotive', 'closed', 'wagon'], + options: [ + 'wagon', + 'wagon-end-left', + 'wagon-end-right', + 'couchette', + 'sleeping', + 'restaurant', + 'locomotive', + 'closed', + ], }; const wagonClass: InputType = { control: { type: 'inline-radio', }, - options: ['1', '2'], + options: ['1', '2', null], }; const defaultArgTypes: ArgTypes = { @@ -76,10 +81,10 @@ const defaultArgTypes: ArgTypes = { const defaultArgs: Args = { label: '36', - type: type.options![2], + type: type.options![0], occupancy: occupancy.options![2], 'wagon-class': wagonClass.options![1], - 'additional-accessibility-text': undefined, + 'additional-accessibility-text': '', }; export const wagonLowOccupancy: StoryObj = { @@ -88,20 +93,22 @@ export const wagonLowOccupancy: StoryObj = { args: defaultArgs, }; -export const wagonMediumOccupancy: StoryObj = { +export const wagonEndLeftMediumOccupancy: StoryObj = { render: Template, argTypes: defaultArgTypes, args: { ...defaultArgs, + type: type.options![1], occupancy: occupancy.options![1], }, }; -export const wagonHighOccupancy: StoryObj = { +export const wagonEndRightHighOccupancy: StoryObj = { render: Template, argTypes: defaultArgTypes, args: { ...defaultArgs, + type: type.options![2], occupancy: occupancy.options![0], }, }; @@ -115,42 +122,45 @@ export const wagonNoneOccupancy: StoryObj = { }, }; -export const wagonUndefinedOccupancy: StoryObj = { +export const couchette: StoryObj = { render: Template, argTypes: defaultArgTypes, args: { ...defaultArgs, - occupancy: '', + type: type.options![3], + 'wagon-class': '', + occupancy: null, }, }; -export const wagonOneIcon: StoryObj = { - render: WagonOneIconTemplate, +export const sleeping: StoryObj = { + render: Template, argTypes: defaultArgTypes, - args: defaultArgs, + args: { + ...defaultArgs, + type: type.options![4], + 'wagon-class': '', + occupancy: null, + }, }; -export const wagonMultipleIcons: StoryObj = { +export const RestaurantIcons: StoryObj = { render: WagonIconsTemplate, argTypes: defaultArgTypes, - args: defaultArgs, -}; - -export const wagonFirstClass: StoryObj = { - render: Template, - argTypes: defaultArgTypes, args: { ...defaultArgs, - 'wagon-class': wagonClass.options![0], + type: type.options![5], + 'wagon-class': '', + occupancy: null, }, }; -export const wagonUndefinedClass: StoryObj = { +export const wagonFirstClass: StoryObj = { render: Template, argTypes: defaultArgTypes, args: { ...defaultArgs, - 'wagon-class': undefined, + 'wagon-class': wagonClass.options![0], }, }; @@ -159,7 +169,9 @@ export const locomotive: StoryObj = { argTypes: defaultArgTypes, args: { ...defaultArgs, - type: type.options![0], + type: type.options![6], + 'wagon-class': '', + occupancy: null, }, }; @@ -168,7 +180,7 @@ export const closed: StoryObj = { argTypes: defaultArgTypes, args: { ...defaultArgs, - type: type.options![1], + type: type.options![7], }, }; diff --git a/src/elements/train/train-wagon/train-wagon.ts b/src/elements/train/train-wagon/train-wagon.ts index a29ab33b07..bd5797b3f5 100644 --- a/src/elements/train/train-wagon/train-wagon.ts +++ b/src/elements/train/train-wagon/train-wagon.ts @@ -1,12 +1,7 @@ -import { - type CSSResultGroup, - LitElement, - nothing, - type PropertyValues, - type TemplateResult, -} from 'lit'; +import { type CSSResultGroup, LitElement, nothing, type TemplateResult } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { html, unsafeStatic } from 'lit/static-html.js'; +import { when } from 'lit/directives/when.js'; +import { html } from 'lit/static-html.js'; import { SbbLanguageController } from '../../core/controllers.js'; import { forceType, handleDistinctChange, omitEmptyConverter } from '../../core/decorators.js'; @@ -16,8 +11,11 @@ import { i18nBlockedPassage, i18nClass, i18nClosedCompartmentLabel, + i18nCouchetteWagonLabel, i18nLocomotiveLabel, + i18nRestaurantWagonLabel, i18nSector, + i18nSleepingWagonLabel, i18nWagonLabel, i18nWagonLabelNumber, } from '../../core/i18n.js'; @@ -30,6 +28,12 @@ import style from './train-wagon.scss?lit&inline'; import '../../icon.js'; import '../../timetable-occupancy-icon.js'; +const typeToIconMap: Record = { + couchette: 'sa-cc', + sleeping: 'sa-wl', + restaurant: 'sa-wr', +}; + /** * It displays a train compartment within a `sbb-train` component. * @@ -44,15 +48,27 @@ class SbbTrainWagonElement extends SbbNamedSlotListMixin e._sectorChanged()) @property({ reflect: true, converter: omitEmptyConverter }) @@ -62,12 +78,12 @@ class SbbTrainWagonElement extends SbbNamedSlotListMixin): void { - super.willUpdate(changedProperties); - - if ( - changedProperties.has('type') || - changedProperties.has('occupancy') || - changedProperties.has('wagonClass') - ) { - this.toggleAttribute( - 'data-has-visible-wagon-content', - Boolean((this.type === 'wagon' && this.occupancy) || this.wagonClass), - ); - } - } - private _sectorChanged(): void { this._sectorChange.emit(); } + private _typeLabel(): string { + if (this.type === 'closed') { + return i18nClosedCompartmentLabel[this._language.current]; + } else if (this.type === 'locomotive') { + return i18nLocomotiveLabel[this._language.current]; + } else if (this.type === 'couchette') { + return i18nCouchetteWagonLabel[this._language.current]; + } else if (this.type === 'sleeping') { + return i18nSleepingWagonLabel[this._language.current]; + } else if (this.type === 'restaurant') { + return i18nRestaurantWagonLabel[this._language.current]; + } + return i18nWagonLabel[this._language.current]; + } + protected override render(): TemplateResult { - const label = (tagName: string): TemplateResult => { - const TAG_NAME = tagName; - /* eslint-disable lit/binding-positions */ - return html` - <${unsafeStatic(TAG_NAME)} class="sbb-train-wagon__label" aria-hidden=${(!this - .label).toString()}> - ${ - this.label - ? html` - ${`${i18nWagonLabelNumber[this._language.current]},`}  - - ${this.label}` - : nothing - } - - `; - }; + const blockedPassage = + this.type === 'wagon-end-left' && this.blockedPassage === 'none' + ? 'previous' + : this.type === 'wagon-end-right' && this.blockedPassage === 'none' + ? 'next' + : (this.blockedPassage ?? 'none'); + + const hasBlockedPassageEntry = blockedPassage !== 'none'; + + // From accessibility perspective we cannot create a list with only one entry. + // We need to know what will be presented and switch semantics based on count. + const listEntriesCount = + +!!this.sector + + +!!this.label + + +(!!this.wagonClass && this.type !== 'closed') + + +(!!this.occupancy && this.type !== 'closed') + + +hasBlockedPassageEntry; const sectorString = `${i18nSector[this._language.current]}, ${this.sector}`; + const labelContent = this.label + ? html` + ${`${i18nWagonLabelNumber[this._language.current]}, ${this.label}`} + + ` + : nothing; + + const wagonClassContent = html` + ${this.wagonClass === '1' + ? i18nClass['first'][this._language.current] + : i18nClass['second'][this._language.current]} + + `; + + const mainIcon = typeToIconMap[this.type] + ? html`` + : nothing; + return html`
      - ${this.type === 'wagon' - ? html`
        + ${when( + listEntriesCount > 1, + () => + html`
          ${this.sector ? html`
        • ${sectorString}
        • ` : nothing} - ${label('li')} - ${this.wagonClass - ? html`
        • - - ${this.wagonClass === '1' - ? i18nClass['first'][this._language.current] - : i18nClass['second'][this._language.current]} - - -
        • ` +
        • + ${labelContent} +
        • + ${this.wagonClass && this.type !== 'closed' + ? html`
        • ${wagonClassContent}
        • ` : nothing} - ${this.occupancy + ${this.occupancy && this.type !== 'closed' ? html`` : nothing} - ${this.blockedPassage && this.blockedPassage !== 'none' + ${hasBlockedPassageEntry ? html`
        • - ${i18nBlockedPassage[this.blockedPassage][this._language.current]} + ${i18nBlockedPassage[blockedPassage][this._language.current]}
        • ` : nothing} -
        ` - : nothing} - ${this.type === 'closed' - ? html` + ${mainIcon} +
      `, + () => + html`
      - ${i18nClosedCompartmentLabel(this.label ? parseInt(this.label) : undefined)[ - this._language.current - ]} - ${this.sector ? `, ${sectorString}` : nothing} + ${`${this._typeLabel()}${this.sector ? `, ${sectorString}` : ''}`} - ${label('span')} - ` - : nothing} - ${this.type === 'locomotive' - ? html` - - ${i18nLocomotiveLabel[this._language.current]} - ${this.sector ? `, ${sectorString}` : nothing} + + ${labelContent} - ${label('span')} - - ` - : nothing} + + ${this.wagonClass && this.type !== 'closed' + ? html`${wagonClassContent}` + : nothing} + ${this.occupancy && this.type !== 'closed' + ? html`` + : nothing} + ${hasBlockedPassageEntry + ? html` + ${i18nBlockedPassage[blockedPassage][this._language.current]} + ` + : nothing} + ${mainIcon} +
      `, + )} ${this.additionalAccessibilityText ? html`, ${this.additionalAccessibilityText}` : nothing} - ${this.type === 'wagon' - ? html` - ${this.renderList({ - class: 'sbb-train-wagon__icons-list', - ariaLabel: i18nAdditionalWagonInformationHeading[this._language.current], - })} - ` - : nothing} + ${this.renderList({ + class: 'sbb-train-wagon__attribute-icon-list', + ariaLabel: i18nAdditionalWagonInformationHeading[this._language.current], + })}
      `; } diff --git a/src/elements/train/train-wagon/train-wagon.visual.spec.ts b/src/elements/train/train-wagon/train-wagon.visual.spec.ts index 9c0f84a584..503e23be7b 100644 --- a/src/elements/train/train-wagon/train-wagon.visual.spec.ts +++ b/src/elements/train/train-wagon/train-wagon.visual.spec.ts @@ -1,53 +1,55 @@ -import { html, nothing } from 'lit'; +import { html, nothing, type TemplateResult } from 'lit'; -import { describeViewports, visualDiffDefault } from '../../core/testing/private.js'; +import { describeEach, describeViewports, visualDiffDefault } from '../../core/testing/private.js'; import './train-wagon.js'; +import '../train-formation.js'; +import '../train.js'; describe(`sbb-train-wagon`, () => { - const occupancyCases = ['high', 'medium', 'low', 'none', undefined]; - const typeCases = ['locomotive', 'closed', 'wagon']; + const wagonTypeCases = { + type: [ + 'wagon', + 'wagon-end-left', + 'wagon-end-right', + 'couchette', + 'sleeping', + 'restaurant', + 'locomotive', + 'closed', + ], + view: ['side', 'top'], + forcedColors: [false, true], + }; + + const occupancyCases = ['high', 'medium', 'low', 'none', null]; const wagonClassCases = [1, 2]; - const labelCases = [undefined, '38']; + + const trainFormationWrapper = (content: TemplateResult): TemplateResult => + html`${content}`; describeViewports({ viewports: ['zero', 'medium'] }, () => { for (const occupancy of occupancyCases) { it( `occupancy=${occupancy}`, - visualDiffDefault.with(async (setup) => { - await setup.withFixture(html` - - `); - }), - ); - } - - for (const type of typeCases) { - it( - `type=${type}`, - visualDiffDefault.with(async (setup) => { - await setup.withFixture(html``); - }), - ); - } - - for (const wagonClass of wagonClassCases) { - it( - `class=${wagonClass}`, visualDiffDefault.with(async (setup) => { await setup.withFixture( - html``, + trainFormationWrapper(html` + + `), ); }), ); } - for (const label of labelCases) { + for (const wagonClass of wagonClassCases) { it( - `label=${label}`, + `class=${wagonClass}`, visualDiffDefault.with(async (setup) => { await setup.withFixture( - html``, + trainFormationWrapper( + html``, + ), ); }), ); @@ -56,25 +58,53 @@ describe(`sbb-train-wagon`, () => { it( `one icon`, visualDiffDefault.with(async (setup) => { - await setup.withFixture(html` - - - - `); + await setup.withFixture( + trainFormationWrapper(html` + + + + `), + ); }), ); it( `multiple icons`, visualDiffDefault.with(async (setup) => { - await setup.withFixture(html` - - - - - - `); + await setup.withFixture( + trainFormationWrapper(html` + + + + + + `), + ); }), ); }); + + describeViewports({ viewports: ['zero'] }, () => { + describeEach(wagonTypeCases, ({ type, view, forcedColors }) => { + it( + '', + visualDiffDefault.with(async (setup) => { + await setup.withFixture( + html` + + ${type === 'sleeping' || type === 'couchette' || type === 'restaurant' + ? html`` + : html``} + + `, + { forcedColors }, + ); + }), + ); + }); + }); }); diff --git a/src/elements/train/train/__snapshots__/train.snapshot.spec.snap.js b/src/elements/train/train/__snapshots__/train.snapshot.spec.snap.js index 64ce5f81c3..09cfe96edd 100644 --- a/src/elements/train/train/__snapshots__/train.snapshot.spec.snap.js +++ b/src/elements/train/train/__snapshots__/train.snapshot.spec.snap.js @@ -16,35 +16,27 @@ snapshots["sbb-train renders Shadow DOM"] =
      Train, Driving direction Bern.
      - +
`; /* end snapshot sbb-train renders Shadow DOM */ diff --git a/src/elements/train/train/readme.md b/src/elements/train/train/readme.md index 2531d78120..20647bd1ba 100644 --- a/src/elements/train/train/readme.md +++ b/src/elements/train/train/readme.md @@ -1,7 +1,7 @@ A `sbb-train` is a component used as a container element for a collection of -[sbb-train-wagon](/docs/timetable-sbb-train-wagon--docs)s -or [sbb-train-blocked-passage](/docs/timetable-sbb-train-blocked-passage--docs)s, -and it can be used within the [sbb-train-formation](/docs/timetable-sbb-train-formation--docs) component. +[sbb-train-wagon](/docs/elements-timetable-sbb-train-wagon--docs)s +or [sbb-train-blocked-passage](/docs/elements-timetable-sbb-train-blocked-passage--docs)s, +and it can be used within the [sbb-train-formation](/docs/elements-timetable-sbb-train-formation--docs) component. ```html { >`, ); - expect( - element.shadowRoot!.querySelector('.sbb-train__direction-arrow')!.getAttribute('name'), - ).to.contain('-left-'); + expect(element.shadowRoot!.querySelector('sbb-icon')!.getAttribute('name')).to.contain( + '-left-', + ); }); it('should display right indicator if direction is right', async () => { @@ -70,9 +70,9 @@ describe(`sbb-train`, () => { >
`, ); - expect( - element.shadowRoot!.querySelector('.sbb-train__direction-arrow')!.getAttribute('name'), - ).to.contain('-right-'); + expect(element.shadowRoot!.querySelector('sbb-icon')!.getAttribute('name')).to.contain( + '-right-', + ); }); describe('accessibility label', () => { diff --git a/src/elements/train/train/train.stories.ts b/src/elements/train/train/train.stories.ts index 6b1f730474..016a87fb6d 100644 --- a/src/elements/train/train/train.stories.ts +++ b/src/elements/train/train/train.stories.ts @@ -62,7 +62,7 @@ const defaultArgTypes: ArgTypes = { 'accessibility-label': accessibilityLabel, station, direction, - directionLabelLevel, + 'direction-label-level': directionLabelLevel, }; const defaultArgs: Args = { diff --git a/src/elements/train/train/train.ts b/src/elements/train/train/train.ts index 701929c4b3..08b820cb19 100644 --- a/src/elements/train/train/train.ts +++ b/src/elements/train/train/train.ts @@ -9,7 +9,7 @@ import { customElement, property } from 'lit/decorators.js'; import { html, unsafeStatic } from 'lit/static-html.js'; import { SbbLanguageController } from '../../core/controllers.js'; -import { forceType } from '../../core/decorators.js'; +import { forceType, omitEmptyConverter } from '../../core/decorators.js'; import { EventEmitter } from '../../core/eventing.js'; import { i18nTrain, i18nWagonsLabel } from '../../core/i18n.js'; import { SbbNamedSlotListMixin, type WithListChildren } from '../../core/mixins.js'; @@ -43,7 +43,7 @@ class SbbTrainElement extends SbbNamedSlotListMixin< /** General label for "driving direction". */ @forceType() - @property({ attribute: 'direction-label' }) + @property({ attribute: 'direction-label', reflect: true, converter: omitEmptyConverter }) public accessor directionLabel: string = ''; /** Heading level of the direction label, used for screen readers. */ @@ -112,33 +112,29 @@ class SbbTrainElement extends SbbNamedSlotListMixin< <${unsafeStatic(TITLE_TAG_NAME)} class="sbb-train__direction-label-sr"> ${this._getDirectionAriaLabel()} - ${this.renderList({ - class: 'sbb-train__wagons', - ariaLabel: i18nWagonsLabel[this._language.current], - })} - ${ this.directionLabel - ? html` `; } diff --git a/src/elements/train/train/train.visual.spec.ts b/src/elements/train/train/train.visual.spec.ts index 4a6f7673dc..e87ba18005 100644 --- a/src/elements/train/train/train.visual.spec.ts +++ b/src/elements/train/train/train.visual.spec.ts @@ -33,5 +33,19 @@ describe(`sbb-train`, () => { }), ); }); + + it( + 'display ellipsis with long label', + visualDiffDefault.with(async (setup) => { + await setup.withFixture( + html``, + { maxWidth: '200px' }, + ); + }), + ); }); }); diff --git a/tools/web-test-runner/preload-icons.ts b/tools/web-test-runner/preload-icons.ts index f647c2f9cc..04c7513b54 100644 --- a/tools/web-test-runner/preload-icons.ts +++ b/tools/web-test-runner/preload-icons.ts @@ -85,6 +85,7 @@ const preloadIconList = [ 'sa-abteilkinderwagen', 'sa-b', 'sa-bz', + 'sa-cc', 'sa-ci', 'sa-fz', 'sa-nf', @@ -93,6 +94,7 @@ const preloadIconList = [ 'sa-rs', 'sa-tg', 'sa-vr', + 'sa-wl', 'sa-wr', 'sa-z', 'shopping-cart-small',