Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/dry-moles-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'polaris.shopify.com': minor
'@shopify/polaris': patch
---

Updated alias and scale types in `Box` with type tests to check they exist in our token groups.
Updated `get-props` script to parse utility types with unions.
145 changes: 109 additions & 36 deletions polaris-react/src/components/Box/Box.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,123 @@
import React, {createElement, forwardRef, ReactNode} from 'react';
import type {
ColorsTokenName,
DepthShadowAlias,
ShapeTokenName,
SpacingSpaceScale,
} from '@shopify/polaris-tokens';

import {classNames, sanitizeCustomProperties} from '../../utilities/css';

import styles from './Box.scss';

type BackgroundColorTokenScale = Extract<
ColorsTokenName,
| 'background'
| `background-${string}`
| 'surface'
| `surface-${string}`
type Element = 'div' | 'span';

export type BackgroundColorTokenScale =
| 'action-critical'
| 'action-critical-depressed'
| 'action-critical-disabled'
| 'action-critical-hovered'
| 'action-critical-pressed'
| 'action-primary'
| 'action-primary-depressed'
| 'action-primary-disabled'
| 'action-primary-hovered'
| 'action-primary-pressed'
| 'action-secondary'
| 'action-secondary-depressed'
| 'action-secondary-disabled'
| 'action-secondary-hovered'
| 'action-secondary-hovered-dark'
| 'action-secondary-pressed'
| 'action-secondary-pressed-dark'
| 'backdrop'
| 'background'
| 'background-hovered'
| 'background-pressed'
| 'background-selected'
| 'overlay'
| `action-${string}`
>;
type ColorTokenScale = Extract<ColorsTokenName, 'text' | `text-${string}`>;

type BorderShapeTokenScale = ShapeTokenName extends `border-${infer Scale}`
? Scale
: never;
type BorderTokenScale = Exclude<
BorderShapeTokenScale,
`radius-${string}` | `width-${string}`
>;
| 'surface'
| 'surface-attention'
| 'surface-critical'
| 'surface-critical-subdued'
| 'surface-critical-subdued-depressed'
| 'surface-critical-subdued-hovered'
| 'surface-critical-subdued-pressed'
| 'surface-dark'
| 'surface-depressed'
| 'surface-disabled'
| 'surface-highlight'
| 'surface-highlight-subdued'
| 'surface-highlight-subdued-hovered'
| 'surface-highlight-subdued-pressed'
| 'surface-hovered'
| 'surface-hovered-dark'
| 'surface-neutral'
| 'surface-neutral-disabled'
| 'surface-neutral-hovered'
| 'surface-neutral-pressed'
| 'surface-neutral-subdued'
| 'surface-neutral-subdued-dark'
| 'surface-pressed'
| 'surface-pressed-dark'
| 'surface-primary-selected'
| 'surface-primary-selected-hovered'
| 'surface-primary-selected-pressed'
| 'surface-search-field'
| 'surface-search-field-dark'
| 'surface-selected'
| 'surface-selected-hovered'
| 'surface-selected-pressed'
| 'surface-subdued'
| 'surface-success'
| 'surface-success-subdued'
| 'surface-success-subdued-hovered'
| 'surface-success-subdued-pressed'
| 'surface-warning'
| 'surface-warning-subdued'
| 'surface-warning-subdued-hovered'
| 'surface-warning-subdued-pressed';

export type ColorTokenScale =
| 'text'
| 'text-critical'
| 'text-disabled'
| 'text-highlight'
| 'text-on-critical'
| 'text-on-dark'
| 'text-on-interactive'
| 'text-on-primary'
| 'text-primary'
| 'text-primary-hovered'
| 'text-primary-pressed'
| 'text-subdued'
| 'text-subdued-on-dark'
| 'text-success'
| 'text-warning';

export type BorderTokenAlias =
| 'base'
| 'dark'
| 'divider'
| 'divider-on-dark'
| 'transparent';

interface Border {
bottom: BorderTokenScale;
left: BorderTokenScale;
right: BorderTokenScale;
top: BorderTokenScale;
bottom: BorderTokenAlias;
left: BorderTokenAlias;
right: BorderTokenAlias;
top: BorderTokenAlias;
}

type BorderRadiusTokenScale = Extract<
BorderShapeTokenScale,
`radius-${string}`
> extends `radius-${infer Scale}`
? Scale
: never;
export type BorderRadiusTokenScale =
| '05'
| '1'
| '2'
| '3'
| '4'
| '5'
| '6'
| 'base'
| 'large'
| 'half';

interface BorderRadius {
bottomLeft: BorderRadiusTokenScale;
Expand All @@ -58,23 +133,21 @@ interface Spacing {
top: SpacingSpaceScale;
}

type Element = 'div' | 'span';

export interface BoxProps {
/** HTML Element type */
as?: Element;
/** Background color */
background?: BackgroundColorTokenScale;
/** Border style */
border?: BorderTokenScale;
border?: BorderTokenAlias;
/** Bottom border style */
borderBottom?: BorderTokenScale;
borderBottom?: BorderTokenAlias;
/** Left border style */
borderLeft?: BorderTokenScale;
borderLeft?: BorderTokenAlias;
/** Right border style */
borderRight?: BorderTokenScale;
borderRight?: BorderTokenAlias;
/** Top border style */
borderTop?: BorderTokenScale;
borderTop?: BorderTokenAlias;
/** Border radius */
borderRadius?: BorderRadiusTokenScale;
/** Bottom left border radius */
Expand Down
55 changes: 55 additions & 0 deletions polaris-react/src/components/Box/tests/Box.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,62 @@
import React from 'react';
import {mountWithApp} from 'tests/utilities';
import type {ColorsTokenName, ShapeTokenName} from '@shopify/polaris-tokens';

import {Box} from '..';
import type {
BackgroundColorTokenScale as BoxBackgroundColorTokenScale,
ColorTokenScale as BoxColorTokenScale,
BorderTokenAlias as BoxBorderTokenAlias,
BorderRadiusTokenScale as BoxBorderRadiusTokenScale,
} from '..';

// Test that type passed in is true
type Expect<T extends true> = T;
// Test each token in TokenTypeAlias to check that it exists in the TokenGroup
type Equal<TokenGroup, TokenTypeAlias> = (<T>() => T extends TokenGroup
? 1
: 2) extends <T>() => T extends TokenTypeAlias ? 1 : 2
? true
: false;

// Extract token scales and aliases from token groups for testing
type BackgroundColorTokenScale = Extract<
ColorsTokenName,
| `action-${string}`
| 'backdrop'
| 'background'
| `background-${string}`
| 'overlay'
| 'surface'
| `surface-${string}`
>;

type ColorTokenScale = Extract<ColorsTokenName, 'text' | `text-${string}`>;

type BorderShapeTokenScale = ShapeTokenName extends `border-${infer Scale}`
? Scale
: never;
type BorderTokenAlias = Exclude<
BorderShapeTokenScale,
`radius-${string}` | `width-${string}`
>;

type BorderRadiusTokenScale = Extract<
BorderShapeTokenScale,
`radius-${string}`
> extends `radius-${infer Scale}`
? Scale
: never;

// Test type aliases to ensure they are valid values from our token groups
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
type cases = [
Expect<Equal<BackgroundColorTokenScale, BoxBackgroundColorTokenScale>>,
Expect<Equal<ColorTokenScale, BoxColorTokenScale>>,
Expect<Equal<BorderTokenAlias, BoxBorderTokenAlias>>,
Expect<Equal<BorderRadiusTokenScale, BoxBorderRadiusTokenScale>>,
];

const text = 'This is a box';
const children = <p>{text}</p>;
Expand Down
7 changes: 7 additions & 0 deletions polaris.shopify.com/scripts/get-props/src/get-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,15 @@ const parseTypeAliasDeclaration: NodeParser = (
const description = getSymbolComment(symbol, checker);
const name = symbol.escapedName.toString();
const syntaxKind = ts.SyntaxKind[typeAliasDeclaration.kind];
const typeRefNode = typeAliasDeclaration.type as ts.TypeReferenceNode;
let value = typeAliasDeclaration.type.getText();

for (const typeArg of typeRefNode.typeArguments ?? []) {
if (typeArg.kind === ts.SyntaxKind.UnionType) {
value = checker.typeToString(checker.getTypeAtLocation(typeArg));
}
}

if (typeAliasDeclaration.type.kind === ts.SyntaxKind.UnionType) {
const unionType = typeAliasDeclaration.type as ts.UnionTypeNode;
value = unionType.types.map((type) => type.getText()).join(' | ');
Expand Down
Loading