Skip to content

Commit 20a21b5

Browse files
adamkudrnacurdaj
authored andcommitted
Refactor(web, web-react): Introduce Shape Variant dictionary and use it for NavigationAction
1 parent bc74c6a commit 20a21b5

File tree

19 files changed

+203
-142
lines changed

19 files changed

+203
-142
lines changed

docs/DICTIONARIES.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This project uses `dictionaries` to unify props between different components.
1616
- [Placement](#placement)
1717
- [Size](#size)
1818
- [Validation](#validation)
19+
- [Variant](#variant)
1920

2021
### Alignment
2122

@@ -81,6 +82,7 @@ This project uses `dictionaries` to unify props between different components.
8182

8283
### Variant
8384

84-
| Dictionary | Values | Code name |
85-
| ------------ | ----------------- | ----------- |
86-
| Fill Variant | `fill`, `outline` | FillVariant |
85+
| Dictionary | Values | Code name |
86+
| ------------- | ----------------- | ------------ |
87+
| Fill Variant | `fill`, `outline` | FillVariant |
88+
| Shape Variant | `box`, `pill` | ShapeVariant |

packages/web-react/src/components/Navigation/NavigationAction.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
'use client';
22

33
import React, { ElementType, forwardRef, ReactElement } from 'react';
4+
import { ShapeVariants } from '../../constants';
45
import { useStyleProps } from '../../hooks';
56
import { PolymorphicRef, SpiritNavigationActionProps } from '../../types';
67
import { mergeStyleProps } from '../../utils';
7-
import { NavigationActionVariants } from './constants';
88
import { useNavigationActionProps } from './useNavigationActionProps';
99
import { useNavigationStyleProps } from './useNavigationStyleProps';
1010

1111
const defaultProps: Partial<SpiritNavigationActionProps> = {
1212
elementType: 'a',
13-
variant: NavigationActionVariants.BOX,
13+
variant: ShapeVariants.BOX,
1414
};
1515

1616
/* We need an exception for components exported with forwardRef */

packages/web-react/src/components/Navigation/README.md

+13-12
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ it will apply a gap between them.
3434
| Name | Type | Default | Required | Description |
3535
| ----------- | ------------------------------------------------------------------------------------------ | ------------ | -------- | ----------------------------- |
3636
| `children` | \[`ReactElement<HTMLLIElement>` \| `ReactElement<NavigationItem>` \| Array of these types] | `null` || Content of the Navigation |
37-
| `direction` | [Direction dictionary][direction-dictionary] | `horizontal` || Orientation of the Navigation |
37+
| `direction` | [Direction dictionary][dictionary-direction] | `horizontal` || Orientation of the Navigation |
3838

3939
The components accept [additional attributes][readme-additional-attributes].
4040
If you need more control over the styling of a component, you can use [style props][readme-style-props]
@@ -107,16 +107,16 @@ inherit the height of the `Header`.
107107

108108
### API
109109

110-
| Name | Type | Default | Required | Description |
111-
| ------------- | --------------------------------- | ------- | -------- | ------------------------------- |
112-
| `children` | \[`string` \| `ReactNode`] | `null` || Content of the NavigationAction |
113-
| `elementType` | `ElementType` | `a` || Type of element used as |
114-
| `href` | `string` | - || URL of the link |
115-
| `isDisabled` | `boolean` | `false` || Whether the action is disabled |
116-
| `isSelected` | `boolean` | `false` || Whether the action is selected |
117-
| `ref` | `ForwardedRef<HTMLAnchorElement>` ||| Anchor element reference |
118-
| `target` | `string` | `null` || Link target |
119-
| `variant` | \[`box` \| `pill`] | `box` || Variant of the NavigationAction |
110+
| Name | Type | Default | Required | Description |
111+
| ------------- | ---------------------------------------------- | ------- | -------- | ------------------------------- |
112+
| `children` | \[`string` \| `ReactNode`] | `null` || Content of the NavigationAction |
113+
| `elementType` | `ElementType` | `a` || Type of element used as |
114+
| `href` | `string` | - || URL of the link |
115+
| `isDisabled` | `bool` | `false` || Whether the action is disabled |
116+
| `isSelected` | `bool` | `false` || Whether the action is selected |
117+
| `ref` | `ForwardedRef<HTMLAnchorElement>` ||| Anchor element reference |
118+
| `target` | `string` | `null` || Link target |
119+
| `variant` | [Shape Variant Dictionary][dictionary-variant] | `box` || Variant of the NavigationAction |
120120

121121
The components accept [additional attributes][readme-additional-attributes].
122122
If you need more control over the styling of a component, you can use [style props][readme-style-props]
@@ -207,7 +207,8 @@ With Buttons:
207207
</Navigation>
208208
```
209209

210-
[direction-dictionary]: https://github.com/lmc-eu/spirit-design-system/blob/main/docs/DICTIONARIES.md#direction
210+
[dictionary-direction]: https://github.com/lmc-eu/spirit-design-system/blob/main/docs/DICTIONARIES.md#direction
211+
[dictionary-variant]: https://github.com/lmc-eu/spirit-design-system/tree/main/docs/DICTIONARIES.md#variant
211212
[readme-additional-attributes]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#additional-attributes
212213
[readme-escape-hatches]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#escape-hatches
213214
[readme-style-props]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#style-props

packages/web-react/src/components/Navigation/__tests__/useNavigationStyleProps.test.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { renderHook } from '@testing-library/react';
2-
import { Direction } from '../../../constants';
2+
import { Direction, ShapeVariants } from '../../../constants';
33
import { SpiritNavigationActionProps } from '../../../types';
4-
import { NavigationActionVariants } from '../constants';
54
import { useNavigationStyleProps } from '../useNavigationStyleProps';
65

76
const navigationActionVariantDataProvider = [
87
{
9-
variant: NavigationActionVariants.BOX,
8+
variant: ShapeVariants.BOX,
109
className: 'NavigationAction--box',
1110
description: 'box variant',
1211
},
1312
{
14-
variant: NavigationActionVariants.PILL,
13+
variant: ShapeVariants.PILL,
1514
className: 'NavigationAction--pill',
1615
description: 'pill variant',
1716
},
@@ -49,7 +48,7 @@ describe('useNavigationStyleProps', () => {
4948
);
5049

5150
it('should return pill variant default class', () => {
52-
const props = { variant: NavigationActionVariants.PILL };
51+
const props = { variant: ShapeVariants.PILL };
5352
const { result } = renderHook(() => useNavigationStyleProps(props));
5453

5554
expect(result.current.classProps.action).toBe('NavigationAction NavigationAction--pill');

packages/web-react/src/components/Navigation/constants.ts

-4
This file was deleted.

packages/web-react/src/components/Navigation/stories/NavigationAction.stories.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Markdown } from '@storybook/blocks';
22
import type { Meta, StoryObj } from '@storybook/react';
33
import React from 'react';
4-
import { NavigationActionVariants } from '../constants';
4+
import { ShapeVariants } from '../../../constants';
55
import Navigation from '../Navigation';
66
import NavigationAction from '../NavigationAction';
77
import NavigationItem from '../NavigationItem';
@@ -30,17 +30,17 @@ const meta: Meta<typeof NavigationAction> = {
3030
},
3131
variant: {
3232
control: 'select',
33-
options: [...Object.values(NavigationActionVariants)],
33+
options: [...Object.values(ShapeVariants)],
3434
table: {
35-
defaultValue: { summary: NavigationActionVariants.BOX },
35+
defaultValue: { summary: ShapeVariants.BOX },
3636
},
3737
},
3838
},
3939
args: {
4040
children: 'Link',
4141
isDisabled: false,
4242
isSelected: false,
43-
variant: NavigationActionVariants.BOX,
43+
variant: ShapeVariants.BOX,
4444
},
4545
};
4646

packages/web-react/src/components/Navigation/useNavigationStyleProps.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import classNames from 'classnames';
2-
import { AlignmentYExtended, Direction } from '../../constants';
2+
import { AlignmentYExtended, Direction, ShapeVariants } from '../../constants';
33
import { AlignmentPropertyType, useAlignmentClass, useClassNamePrefix } from '../../hooks';
44
import {
55
DirectionDictionaryType,
@@ -8,7 +8,6 @@ import {
88
SpiritNavigationItemAlignmentYType,
99
SpiritNavigationItemProps,
1010
} from '../../types';
11-
import { NavigationActionVariants } from './constants';
1211

1312
export interface UseNavigationStyleProps {
1413
alignmentY?: SpiritNavigationItemAlignmentYType;
@@ -35,7 +34,7 @@ export const useNavigationStyleProps = ({
3534
isSquare = false,
3635
alignmentY = AlignmentYExtended.CENTER,
3736
direction = Direction.HORIZONTAL,
38-
variant = NavigationActionVariants.BOX,
37+
variant = ShapeVariants.BOX,
3938
...restProps
4039
}: UseNavigationStyleProps): UseNavigationStyleReturn => {
4140
const navigationClass = useClassNamePrefix('Navigation');

packages/web-react/src/components/UNSTABLE_Header/demo/HeaderWithNavigation/MainNavigation.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import React from 'react';
2-
import { Direction } from '../../../../constants';
2+
import { Direction, ShapeVariants } from '../../../../constants';
33
import { NavigationActionVariantsType, SpiritNavigationProps } from '../../../../types';
44
import { Navigation, NavigationAction, NavigationItem } from '../../../Navigation';
5-
import { NavigationActionVariants } from '../../../Navigation/constants';
65

76
interface MainNavigationProps extends Partial<SpiritNavigationProps> {
87
variant?: NavigationActionVariantsType;
98
}
109

1110
export const MainNavigation = ({
1211
direction = Direction.HORIZONTAL,
13-
variant = NavigationActionVariants.BOX,
12+
variant = ShapeVariants.BOX,
1413
}: Partial<MainNavigationProps>) => (
1514
<Navigation
1615
aria-label="Main Navigation"

packages/web-react/src/components/UNSTABLE_Header/demo/HeaderWithPillNavigation.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import React, { useState } from 'react';
22
import { Container } from '../../Container';
33
import { Drawer, DrawerCloseButton, DrawerPanel } from '../../Drawer';
44
import { Flex } from '../../Flex';
5-
import { NavigationActionVariants } from '../../Navigation/constants';
65
import { ProductLogo } from '../../ProductLogo';
76
import { defaultSvgLogo } from '../../ProductLogo/demo/ProductLogoDefault';
87
import { Stack } from '../../Stack';
@@ -26,7 +25,7 @@ const HeaderWithPillNavigation = () => {
2625
<UNSTABLE_HeaderLogo href="#">
2726
<ProductLogo>{defaultSvgLogo}</ProductLogo>
2827
</UNSTABLE_HeaderLogo>
29-
<MainNavigation variant={NavigationActionVariants.PILL} />
28+
<MainNavigation variant="pill" />
3029
<SecondaryHorizontalNavigation id="drawer-navigation-pill" handleOpenDrawer={() => setDrawerOpen(true)} />
3130
</Flex>
3231
</Container>
@@ -37,7 +36,7 @@ const HeaderWithPillNavigation = () => {
3736
<DrawerCloseButton />
3837
<Stack hasIntermediateDividers hasSpacing marginY="space-900" spacing="space-900">
3938
<ProfileNavigation />
40-
<MainNavigation direction="vertical" variant={NavigationActionVariants.PILL} />
39+
<MainNavigation direction="vertical" variant="pill" />
4140
<SecondaryVerticalNavigation />
4241
</Stack>
4342
</DrawerPanel>

packages/web-react/src/constants/dictionaries.ts

+5
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,8 @@ export const FillVariants = {
138138
FILL: 'fill',
139139
OUTLINE: 'outline',
140140
} as const;
141+
142+
export const ShapeVariants = {
143+
BOX: 'box',
144+
PILL: 'pill',
145+
} as const;

packages/web-react/src/types/navigation.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import { ElementType, ReactElement, ReactNode } from 'react';
22
import { NavigationItem } from '../components';
3-
import { NavigationActionVariants } from '../components/Navigation/constants';
43
import { LinkTarget } from './link';
54
import {
65
AlignmentYExtendedDictionaryType,
76
AriaLabelingProps,
87
ChildrenProps,
98
DirectionDictionaryType,
9+
ShapeVariantDictionaryType,
1010
SpiritPolymorphicElementPropsWithRef,
1111
StyleProps,
1212
TransferProps,
1313
} from './shared';
1414

15-
export type NavigationActionVariantsKeys = keyof typeof NavigationActionVariants;
16-
export type NavigationActionVariantsType<T = undefined> =
17-
| (typeof NavigationActionVariants)[NavigationActionVariantsKeys]
18-
| T;
15+
type NonNullableShapeVariant = NonNullable<ShapeVariantDictionaryType>;
16+
17+
export type NavigationActionVariantsType = NonNullableShapeVariant | Record<string, NonNullableShapeVariant>;
1918

2019
export interface NavigationActionBaseProps extends ChildrenProps, StyleProps, AriaLabelingProps, TransferProps {
2120
/** NavigationAction's href attribute */

packages/web-react/src/types/shared/dictionaries.ts

+25-5
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,39 @@ import {
33
AlignmentXExtended,
44
AlignmentY,
55
AlignmentYExtended,
6+
BackgroundColors,
67
BorderColors,
8+
BorderRadii,
9+
BorderStyles,
10+
BorderWidths,
711
ComponentButtonColors,
812
EmotionColors,
913
Emphasis,
1014
LinkColors,
1115
Placements,
16+
ShapeVariants,
1217
Sizes,
1318
SizesExtended,
19+
TextAlignments,
1420
TextColors,
1521
ValidationStates,
16-
BackgroundColors,
17-
BorderStyles,
18-
BorderRadii,
19-
BorderWidths,
20-
TextAlignments,
2122
} from '../../constants';
2223

24+
/**
25+
* Allow autocomplete for string literals.
26+
*
27+
* This is a Typescript quirk.
28+
*
29+
* @see { @link https://github.com/microsoft/TypeScript/issues/29729}
30+
*
31+
* When you want to allow any string, but still give suggestions for known string literals.
32+
* This works because it prevents TypeScript from eagerly collapsing string | "literal" into just string.
33+
* It behaves exactly the same way as string, but with autocomplete added.
34+
* Someday, string | "literal" will just work.
35+
*/
36+
// eslint-disable-next-line @typescript-eslint/ban-types
37+
type AutocompleteStringLiteral = string & {};
38+
2339
/* Alignment */
2440
export type AlignmentXDictionaryKeys = keyof typeof AlignmentX;
2541
export type AlignmentXDictionaryType<T = undefined> = (typeof AlignmentX)[AlignmentXDictionaryKeys] | T;
@@ -105,3 +121,7 @@ export type ValidationStatesDictionaryKeys = keyof typeof ValidationStates;
105121
export type ValidationStatesDictionaryType<T = undefined> =
106122
| (typeof ValidationStates)[ValidationStatesDictionaryKeys]
107123
| T;
124+
125+
/* Variant */
126+
export type ShapeVariantDictionaryKeys = keyof typeof ShapeVariants;
127+
export type ShapeVariantDictionaryType = (typeof ShapeVariants)[ShapeVariantDictionaryKeys] | AutocompleteStringLiteral;

packages/web/src/scss/components/Navigation/_NavigationAction.scss

+21-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// so we need to disable pointer events on it.
44
// 3. We want the selected and expanded states to override the hover and active states.
55

6+
@use '@tokens' as tokens;
7+
@use '../../tools/dictionaries';
68
@use '../../tools/reset';
79
@use '../../tools/typography';
810
@use 'theme';
@@ -16,7 +18,10 @@
1618
display: flex;
1719
gap: theme.$action-gap;
1820
align-items: center;
21+
padding: var(--#{tokens.$css-variable-prefix}navigation-action-padding-y)
22+
var(--#{tokens.$css-variable-prefix}navigation-action-padding-x);
1923
color: theme.$action-color-state-default;
24+
border-radius: var(--#{tokens.$css-variable-prefix}navigation-action-border-radius);
2025
background-color: theme.$action-background-color-state-default;
2126

2227
@media (hover: hover) {
@@ -34,17 +39,28 @@
3439
}
3540
}
3641

37-
.NavigationAction--pill {
38-
padding-block: theme.$action-pill-padding-y;
39-
border-radius: theme.$action-pill-border-radius;
40-
}
41-
4242
.NavigationAction--box::after {
4343
content: '';
4444
position: absolute;
4545
background-color: theme.$action-stripe-background-color-state-default;
4646
}
4747

48+
.Navigation--horizontal {
49+
@include dictionaries.generate-variants(
50+
$class-name: 'NavigationAction',
51+
$dictionary-values: theme.$action-shape-variant-dictionary,
52+
$config: theme.$horizontal-action-shape-variant-config
53+
);
54+
}
55+
56+
.Navigation--vertical {
57+
@include dictionaries.generate-variants(
58+
$class-name: 'NavigationAction',
59+
$dictionary-values: theme.$action-shape-variant-dictionary,
60+
$config: theme.$vertical-action-shape-variant-config
61+
);
62+
}
63+
4864
// stylelint-disable-next-line selector-max-compound-selectors, selector-max-specificity -- 1.
4965
.Navigation--vertical
5066
> ul
@@ -72,7 +88,6 @@
7288
@include typography.generate(theme.$horizontal-action-typography);
7389

7490
justify-content: center;
75-
padding-inline: theme.$action-padding-x;
7691
}
7792

7893
.Navigation--horizontal .NavigationAction--box::after {
@@ -85,7 +100,6 @@
85100
@include typography.generate(theme.$vertical-action-typography);
86101

87102
justify-content: space-between;
88-
padding-block: theme.$vertical-action-spacing-y;
89103

90104
&::after {
91105
inset-block: 0;
@@ -95,15 +109,6 @@
95109
}
96110
}
97111

98-
.Navigation--vertical .NavigationAction--box {
99-
padding-inline: theme.$vertical-action-box-padding-x;
100-
border-radius: theme.$vertical-action-box-border-radius;
101-
}
102-
103-
.Navigation--vertical .NavigationAction--pill {
104-
padding-inline: theme.$vertical-action-pill-padding-x;
105-
}
106-
107112
.Navigation--horizontal .NavigationAction--box:hover::after {
108113
@media (hover: hover) {
109114
background-color: theme.$horizontal-action-stripe-background-color-state-hover;

0 commit comments

Comments
 (0)