@transferwise/components 0.0.0-experimental-588e71a → 0.0.0-experimental-2c3eb65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/build/ListItem/ListItem.js +1 -0
  2. package/build/ListItem/ListItem.js.map +1 -1
  3. package/build/ListItem/ListItem.mjs +1 -0
  4. package/build/ListItem/ListItem.mjs.map +1 -1
  5. package/build/index.js +0 -4
  6. package/build/index.js.map +1 -1
  7. package/build/index.mjs +0 -2
  8. package/build/index.mjs.map +1 -1
  9. package/build/main.css +4 -178
  10. package/build/styles/ListItem/ListItem.css +4 -0
  11. package/build/styles/main.css +4 -178
  12. package/build/types/ListItem/ListItem.d.ts +1 -1
  13. package/build/types/ListItem/ListItem.d.ts.map +1 -1
  14. package/build/types/index.d.ts +0 -4
  15. package/build/types/index.d.ts.map +1 -1
  16. package/package.json +1 -1
  17. package/src/ListItem/ListItem.css +4 -0
  18. package/src/ListItem/ListItem.less +4 -0
  19. package/src/ListItem/ListItem.test.tsx +9 -0
  20. package/src/ListItem/ListItem.tsx +2 -1
  21. package/src/ListItem/_stories/ListItem.layout.test.story.tsx +1 -1
  22. package/src/ListItem/_stories/ListItem.story.tsx +1 -1
  23. package/src/index.ts +0 -13
  24. package/src/main.css +4 -178
  25. package/src/main.less +0 -2
  26. package/build/CardContainer/CardContainer.js +0 -34
  27. package/build/CardContainer/CardContainer.js.map +0 -1
  28. package/build/CardContainer/CardContainer.mjs +0 -30
  29. package/build/CardContainer/CardContainer.mjs.map +0 -1
  30. package/build/CardWidget/CardWidget.js +0 -98
  31. package/build/CardWidget/CardWidget.js.map +0 -1
  32. package/build/CardWidget/CardWidget.mjs +0 -94
  33. package/build/CardWidget/CardWidget.mjs.map +0 -1
  34. package/build/CardWidget/CardWidgetAction.js +0 -70
  35. package/build/CardWidget/CardWidgetAction.js.map +0 -1
  36. package/build/CardWidget/CardWidgetAction.mjs +0 -65
  37. package/build/CardWidget/CardWidgetAction.mjs.map +0 -1
  38. package/build/styles/CardContainer/CardContainer.css +0 -96
  39. package/build/styles/CardWidget/CardWidget.css +0 -82
  40. package/build/types/CardContainer/CardContainer.d.ts +0 -8
  41. package/build/types/CardContainer/CardContainer.d.ts.map +0 -1
  42. package/build/types/CardContainer/CardContainer.types.d.ts +0 -53
  43. package/build/types/CardContainer/CardContainer.types.d.ts.map +0 -1
  44. package/build/types/CardContainer/_stories/helpers.d.ts +0 -3
  45. package/build/types/CardContainer/_stories/helpers.d.ts.map +0 -1
  46. package/build/types/CardContainer/index.d.ts +0 -3
  47. package/build/types/CardContainer/index.d.ts.map +0 -1
  48. package/build/types/CardWidget/CardWidget.d.ts +0 -10
  49. package/build/types/CardWidget/CardWidget.d.ts.map +0 -1
  50. package/build/types/CardWidget/CardWidget.types.d.ts +0 -55
  51. package/build/types/CardWidget/CardWidget.types.d.ts.map +0 -1
  52. package/build/types/CardWidget/CardWidgetAction.d.ts +0 -54
  53. package/build/types/CardWidget/CardWidgetAction.d.ts.map +0 -1
  54. package/build/types/CardWidget/index.d.ts +0 -4
  55. package/build/types/CardWidget/index.d.ts.map +0 -1
  56. package/src/CardContainer/CardContainer.css +0 -96
  57. package/src/CardContainer/CardContainer.less +0 -164
  58. package/src/CardContainer/CardContainer.test.tsx +0 -150
  59. package/src/CardContainer/CardContainer.tsx +0 -51
  60. package/src/CardContainer/CardContainer.types.ts +0 -60
  61. package/src/CardContainer/_stories/CardContainer.story.tsx +0 -167
  62. package/src/CardContainer/_stories/CardContainer.test.story.tsx +0 -103
  63. package/src/CardContainer/_stories/helpers.tsx +0 -50
  64. package/src/CardContainer/index.ts +0 -6
  65. package/src/CardWidget/CardWidget.css +0 -82
  66. package/src/CardWidget/CardWidget.less +0 -103
  67. package/src/CardWidget/CardWidget.test.tsx +0 -209
  68. package/src/CardWidget/CardWidget.tsx +0 -83
  69. package/src/CardWidget/CardWidget.types.ts +0 -63
  70. package/src/CardWidget/CardWidgetAction.tsx +0 -126
  71. package/src/CardWidget/_stories/CardWidget.story.tsx +0 -238
  72. package/src/CardWidget/_stories/CardWidget.test.story.tsx +0 -350
  73. package/src/CardWidget/index.ts +0 -3
@@ -1,53 +0,0 @@
1
- import type { HTMLAttributes, ReactNode } from 'react';
2
- /**
3
- * Highlights the card, matching `ListItem`'s `spotlight`.
4
- *
5
- * - `active` — live content the user can act on. Raised, with a soft shadow and a hairline border.
6
- * - `inactive` — a feature the user has not set up yet. Recessed, with a dashed stroke.
7
- *
8
- * Without a spotlight the card is flat, with a solid stroke, for content that is present but not
9
- * actionable.
10
- */
11
- export type CardContainerSpotlight = 'active' | 'inactive';
12
- /**
13
- * - `md` — 8px, and 16px from the `md` breakpoint up.
14
- * - `none` — content runs to the card's edges.
15
- */
16
- export type CardContainerPadding = 'md' | 'none';
17
- export interface CardContainerProps extends HTMLAttributes<HTMLElement> {
18
- /**
19
- * Highlights the card as live content (`active`) or as a feature not set up yet (`inactive`).
20
- * Leave unset for a flat card.
21
- * @default undefined
22
- */
23
- spotlight?: CardContainerSpotlight;
24
- /**
25
- * Space between the card's edge and its content. `none` lets content such as images or charts
26
- * run to the card's edges, clipped to its rounded corners. Anything clipped at the edges
27
- * includes focus rings, so use `none` for non-interactive content, and keep lists on `md`.
28
- * @default 'md'
29
- */
30
- padding?: CardContainerPadding;
31
- /**
32
- * Content, stacked with no gap.
33
- * @default undefined
34
- */
35
- children?: ReactNode;
36
- /**
37
- * Element rendered as the root.
38
- * @default 'section'
39
- */
40
- as?: 'section' | 'div' | 'article';
41
- /**
42
- * Shows the card as hovered or pressed while a direct child link (with an `href`) or enabled
43
- * button is, such as a link that fills the card, and as hovered while that child has keyboard
44
- * focus. The rest of the card,
45
- * and controls nested deeper, don't trigger the states, so the card is only shown as something
46
- * to act on where it can be clicked.
47
- * @default false
48
- */
49
- interactive?: boolean;
50
- className?: string;
51
- 'data-testid'?: string;
52
- }
53
- //# sourceMappingURL=CardContainer.types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CardContainer.types.d.ts","sourceRoot":"","sources":["../../../src/CardContainer/CardContainer.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE3D;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,MAAM,CAAC;AAEjD,MAAM,WAAW,kBAAmB,SAAQ,cAAc,CAAC,WAAW,CAAC;IACrE;;;;OAIG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IAEnC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAE/B;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,EAAE,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;IAEnC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
@@ -1,3 +0,0 @@
1
- export declare const SampleContent: () => import("react").JSX.Element;
2
- export declare const CardsList: () => import("react").JSX.Element;
3
- //# sourceMappingURL=helpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../src/CardContainer/_stories/helpers.tsx"],"names":[],"mappings":"AAMA,eAAO,MAAM,aAAa,mCAKzB,CAAC;AAEF,eAAO,MAAM,SAAS,mCAoCrB,CAAC"}
@@ -1,3 +0,0 @@
1
- export { default } from './CardContainer';
2
- export type { CardContainerProps, CardContainerSpotlight, CardContainerPadding, } from './CardContainer.types';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/CardContainer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC"}
@@ -1,10 +0,0 @@
1
- import type { CardWidgetProps } from './CardWidget.types';
2
- /**
3
- * A `CardContainer` with an optional header — a title, a title link and up to two actions — above
4
- * its content. The card shows as hovered or pressed while the title link is, and as hovered while
5
- * the link has keyboard focus. Hovering or pressing the rest of the card, or its actions, leaves it
6
- * at rest.
7
- */
8
- declare const CardWidget: import("react").ForwardRefExoticComponent<CardWidgetProps & import("react").RefAttributes<HTMLElement>>;
9
- export default CardWidget;
10
- //# sourceMappingURL=CardWidget.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CardWidget.d.ts","sourceRoot":"","sources":["../../../src/CardWidget/CardWidget.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA6C5E;;;;;GAKG;AACH,QAAA,MAAM,UAAU,yGAmBf,CAAC;AAIF,eAAe,UAAU,CAAC"}
@@ -1,55 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import type { Heading } from '../common';
3
- import type { CardContainerProps } from '../CardContainer';
4
- import type { LinkProps } from '../Link';
5
- import type { CardWidgetActionButton } from './CardWidgetAction';
6
- /**
7
- * Props passed to the [Link](https://storybook.wise.design/?path=/docs/typography-link--docs)
8
- * that the title is rendered as. A trailing chevron is rendered automatically, so do not put one
9
- * in `title`.
10
- */
11
- export type CardWidgetTitleAction = Pick<LinkProps, 'href' | 'target' | 'onClick'>;
12
- export interface CardWidgetHeader {
13
- /** The widget's title. */
14
- title: ReactNode;
15
- /**
16
- * Heading element rendered for the title.
17
- * @default 'h3'
18
- */
19
- as?: Heading;
20
- /**
21
- * Makes the title a link with a trailing chevron.
22
- * @default undefined
23
- */
24
- titleAction?: CardWidgetTitleAction;
25
- /**
26
- * A `CardWidgetActionButton` object (with a `label` or an `icon`) renders a small button,
27
- * anchored to the inline end. Any other node is rendered as given.
28
- * @default undefined
29
- */
30
- action?: CardWidgetActionButton | ReactNode;
31
- /**
32
- * Second action, at a lower emphasis, rendered before `action`. Two is the maximum, to keep
33
- * the title legible. Takes the same values as `action`, and keeps its lower emphasis when used
34
- * on its own.
35
- * @default undefined
36
- */
37
- actionSecondary?: CardWidgetActionButton | ReactNode;
38
- }
39
- /**
40
- * The widget always has the card's default padding, so its header and content line up, and its
41
- * states always follow its title link.
42
- */
43
- export interface CardWidgetProps extends Omit<CardContainerProps, 'padding' | 'interactive'> {
44
- /**
45
- * The widget's header: a title, optionally a link, and up to two actions.
46
- * @default undefined
47
- */
48
- header?: CardWidgetHeader;
49
- /**
50
- * The widget's content, rendered under the header.
51
- * @default undefined
52
- */
53
- children?: ReactNode;
54
- }
55
- //# sourceMappingURL=CardWidget.types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CardWidget.types.d.ts","sourceRoot":"","sources":["../../../src/CardWidget/CardWidget.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC;AAEnF,MAAM,WAAW,gBAAgB;IAC/B,0BAA0B;IAC1B,KAAK,EAAE,SAAS,CAAC;IAEjB;;;OAGG;IACH,EAAE,CAAC,EAAE,OAAO,CAAC;IAEb;;;OAGG;IACH,WAAW,CAAC,EAAE,qBAAqB,CAAC;IAEpC;;;;OAIG;IACH,MAAM,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAE5C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAC;CACtD;AAED;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,aAAa,CAAC;IAC1F;;;OAGG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAE1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB"}
@@ -1,54 +0,0 @@
1
- import { type MouseEventHandler, type ReactNode } from 'react';
2
- import type { ButtonProps } from '../Button/Button.types';
3
- /** The action slot a `CardWidgetActionButton` was passed to, which is what determines its emphasis. */
4
- export type CardWidgetActionSlot = 'action' | 'actionSecondary';
5
- type CardWidgetActionButtonBase = Pick<ButtonProps, 'href' | 'target' | 'disabled'> & {
6
- /**
7
- * `HTMLElement` rather than a narrower target, because an action is a `button` or an `a`
8
- * depending on whether `href` is set.
9
- * @default undefined
10
- */
11
- onClick?: MouseEventHandler<HTMLElement>;
12
- /**
13
- * Rendered after `label`, or as the only content of an unlabelled action.
14
- * Size it yourself — 16px is expected in both cases.
15
- * @default undefined
16
- */
17
- icon?: ReactNode;
18
- 'data-testid'?: string;
19
- };
20
- /** An action with a visible label, rendered as a small button. */
21
- type CardWidgetLabelledActionButton = CardWidgetActionButtonBase & {
22
- /** Non-rendering values are excluded, so an unlabelled action has to meet the icon-only rules. */
23
- label: Exclude<ButtonProps['children'], null | undefined | boolean>;
24
- /** @default undefined */
25
- 'aria-label'?: string;
26
- };
27
- /**
28
- * An action with no visible label, rendered as a 32px icon button. The icon is
29
- * the only thing on screen and `aria-label` is the only accessible name, so
30
- * both are required.
31
- */
32
- type CardWidgetIconActionButton = CardWidgetActionButtonBase & {
33
- label?: never;
34
- icon: ReactNode;
35
- 'aria-label': string;
36
- };
37
- /**
38
- * An action the `CardWidget` header renders as a button. With a `label` it is a small `Button`; without
39
- * one, a 32px `IconButton`, which requires both an `icon` and an `aria-label`. Emphasis is not
40
- * part of the object — it comes from the slot the action is passed to.
41
- */
42
- export type CardWidgetActionButton = CardWidgetLabelledActionButton | CardWidgetIconActionButton;
43
- /**
44
- * Tells a `CardWidgetActionButton` object apart from any other node passed to an action slot.
45
- * Every button action has a `label` or an `icon`, which no React element, portal or array does.
46
- */
47
- export declare const isCardWidgetActionButton: (value: unknown) => value is CardWidgetActionButton;
48
- /** Renders a `CardWidgetActionButton` at the emphasis of the slot it was passed to. */
49
- declare const CardWidgetAction: ({ action: { label, icon, "aria-label": ariaLabel, "data-testid": dataTestId, href, target, onClick, disabled, }, slot, }: {
50
- action: CardWidgetActionButton;
51
- slot: CardWidgetActionSlot;
52
- }) => import("react").JSX.Element;
53
- export default CardWidgetAction;
54
- //# sourceMappingURL=CardWidgetAction.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CardWidgetAction.d.ts","sourceRoot":"","sources":["../../../src/CardWidget/CardWidgetAction.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAG/E,OAAO,KAAK,EAAkB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG1E,uGAAuG;AACvG,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,iBAAiB,CAAC;AAEhE,KAAK,0BAA0B,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC,GAAG;IACpF;;;;OAIG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;IAEzC;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,kEAAkE;AAClE,KAAK,8BAA8B,GAAG,0BAA0B,GAAG;IACjE,kGAAkG;IAClG,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC;IACpE,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;GAIG;AACH,KAAK,0BAA0B,GAAG,0BAA0B,GAAG;IAC7D,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,8BAA8B,GAAG,0BAA0B,CAAC;AAYjG;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,sBAK5B,CAAC;AAExC,uFAAuF;AACvF,QAAA,MAAM,gBAAgB,GAA6B,0HAYhD;IACD,MAAM,EAAE,sBAAsB,CAAC;IAC/B,IAAI,EAAE,oBAAoB,CAAC;CAC5B,gCAkCA,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -1,4 +0,0 @@
1
- export { default } from './CardWidget';
2
- export type { CardWidgetProps, CardWidgetHeader, CardWidgetTitleAction } from './CardWidget.types';
3
- export type { CardWidgetActionButton } from './CardWidgetAction';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/CardWidget/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AACnG,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -1,96 +0,0 @@
1
- .wds-card-container {
2
- /**
3
- * There is no shadow or elevation token in @transferwise/neptune-tokens, so the active
4
- * spotlight's resting shadow is component-scoped for now, the way PromoCard scopes
5
- * `--Card-box-shadow`. The value is the "Module shadow" effect style from Figma: `#0e0f0c` at
6
- * 4%, kept literal so it doesn't drift with a theme's tokens. Collapses to a token reference
7
- * once one is minted.
8
- */
9
- --CardContainer-shadow-color: rgba(14, 15, 12, 0.04);
10
- --CardContainer-shadow: 0 2px 12px var(--CardContainer-shadow-color), 0 2px 32px var(--CardContainer-shadow-color);
11
- --CardContainer-background-color: var(--color-background-screen);
12
- --CardContainer-border-color: var(--color-border-neutral);
13
- --CardContainer-border-radius: var(--radius-large);
14
- --CardContainer-padding-block: var(--size-8);
15
- --CardContainer-padding-inline: var(--size-8);
16
- background-color: var(--CardContainer-background-color);
17
- border: 1px solid var(--CardContainer-border-color);
18
- border-radius: var(--CardContainer-border-radius);
19
- box-sizing: border-box;
20
- display: flex;
21
- flex-direction: column;
22
- padding-block: var(--CardContainer-padding-block);
23
- padding-inline: var(--CardContainer-padding-inline);
24
- inline-size: 100%;
25
- }
26
- @media (min-width: 840px) {
27
- .wds-card-container {
28
- --CardContainer-padding-block: var(--size-16);
29
- --CardContainer-padding-inline: var(--size-16);
30
- }
31
- }
32
- .wds-card-container--padding-none {
33
- --CardContainer-padding-block: 0;
34
- --CardContainer-padding-inline: 0;
35
- overflow: clip;
36
- }
37
- .wds-card-container--active {
38
- --CardContainer-background-color: var(--color-background-elevated);
39
- --CardContainer-border-color: color-mix(in srgb, var(--color-border-neutral) 25%, transparent);
40
- }
41
- .np-theme-personal:not(.np-theme-personal--dark):not(.np-theme-personal--bright-green):not(.np-theme-personal--forest-green):not(.np-theme-business--dark):not(.np-theme-business--bright-green):not(.np-theme-business--forest-green):not(.np-theme-platform--forest-green) .wds-card-container--active {
42
- box-shadow: var(--CardContainer-shadow);
43
- }
44
- .wds-card-container--inactive {
45
- --CardContainer-background-color: color-mix(in srgb, var(--color-background-neutral) 25%, transparent);
46
- border-width: 0;
47
- padding-block: calc(var(--CardContainer-padding-block) + 1px);
48
- padding-inline: calc(var(--CardContainer-padding-inline) + 1px);
49
- position: relative;
50
- }
51
- .wds-card-container--interactive:has(
52
- > a[href]:hover,
53
- > button:enabled:hover,
54
- > a[href]:focus-visible,
55
- > button:enabled:focus-visible
56
- ).wds-card-container--active {
57
- --CardContainer-border-color: color-mix(in srgb, var(--color-border-neutral) 75%, transparent);
58
- }
59
- .wds-card-container--interactive:has(
60
- > a[href]:hover,
61
- > button:enabled:hover,
62
- > a[href]:focus-visible,
63
- > button:enabled:focus-visible
64
- ).wds-card-container--inactive {
65
- --CardContainer-background-color: color-mix(in srgb, var(--color-background-neutral-hover) 25%, transparent);
66
- }
67
- .wds-card-container--interactive:has(> a[href]:active, > button:enabled:active).wds-card-container--active {
68
- --CardContainer-border-color: var(--color-border-neutral);
69
- }
70
- .wds-card-container--interactive:has(> a[href]:active, > button:enabled:active).wds-card-container--inactive {
71
- --CardContainer-background-color: color-mix(in srgb, var(--color-background-neutral-active) 25%, transparent);
72
- }
73
- .wds-card-container__border {
74
- container-type: size;
75
- inset: 0;
76
- pointer-events: none;
77
- position: absolute;
78
- }
79
- .wds-card-container__border svg {
80
- display: block;
81
- block-size: 100%;
82
- inline-size: 100%;
83
- }
84
- .wds-card-container__border rect {
85
- --CardContainer-border-dash-size: calc(var(--size-12) * 0.5);
86
- --CardContainer-border-stroke-radius: calc(min(var(--CardContainer-border-radius), 50cqh, 50cqw) - 0.5px);
87
- fill: none;
88
- block-size: calc(100% - 1px);
89
- rx: var(--CardContainer-border-stroke-radius);
90
- ry: var(--CardContainer-border-stroke-radius);
91
- stroke: var(--color-border-neutral);
92
- stroke-dasharray: var(--CardContainer-border-dash-size) var(--CardContainer-border-dash-size);
93
- inline-size: calc(100% - 1px);
94
- x: 0.5px;
95
- y: 0.5px;
96
- }
@@ -1,164 +0,0 @@
1
- // The hover and pressed states, as mixins so CardWidget can apply them to the card when only its
2
- // title is hovered or pressed. Applied to the card's root.
3
- .wds-card-container-hover-state() {
4
- // The active spotlight keeps its resting shadow, and its hairline border strengthens.
5
- &.wds-card-container--active {
6
- --CardContainer-border-color: color-mix(in srgb, var(--color-border-neutral) 75%, transparent);
7
- }
8
-
9
- // The inactive spotlight's fill takes its hover value, at the same 25% strength as at rest.
10
- &.wds-card-container--inactive {
11
- --CardContainer-background-color: color-mix(
12
- in srgb,
13
- var(--color-background-neutral-hover) 25%,
14
- transparent
15
- );
16
- }
17
- }
18
-
19
- .wds-card-container-pressed-state() {
20
- // The active spotlight keeps its resting shadow, and its border is at full strength.
21
- &.wds-card-container--active {
22
- --CardContainer-border-color: var(--color-border-neutral);
23
- }
24
-
25
- // The inactive spotlight's fill takes its pressed value, at the same 25% strength as at rest.
26
- &.wds-card-container--inactive {
27
- --CardContainer-background-color: color-mix(
28
- in srgb,
29
- var(--color-background-neutral-active) 25%,
30
- transparent
31
- );
32
- }
33
- }
34
-
35
- .wds-card-container {
36
- /**
37
- * There is no shadow or elevation token in @transferwise/neptune-tokens, so the active
38
- * spotlight's resting shadow is component-scoped for now, the way PromoCard scopes
39
- * `--Card-box-shadow`. The value is the "Module shadow" effect style from Figma: `#0e0f0c` at
40
- * 4%, kept literal so it doesn't drift with a theme's tokens. Collapses to a token reference
41
- * once one is minted.
42
- */
43
- --CardContainer-shadow-color: rgb(14 15 12 / 4%);
44
- --CardContainer-shadow:
45
- 0 2px 12px var(--CardContainer-shadow-color), 0 2px 32px var(--CardContainer-shadow-color);
46
- --CardContainer-background-color: var(--color-background-screen);
47
- --CardContainer-border-color: var(--color-border-neutral);
48
- --CardContainer-border-radius: var(--radius-large);
49
- --CardContainer-padding-block: var(--size-8);
50
- --CardContainer-padding-inline: var(--size-8);
51
-
52
- background-color: var(--CardContainer-background-color);
53
- border: 1px solid var(--CardContainer-border-color);
54
- border-radius: var(--CardContainer-border-radius);
55
- box-sizing: border-box;
56
- display: flex;
57
- flex-direction: column;
58
- padding: var(--CardContainer-padding-block) var(--CardContainer-padding-inline);
59
- width: 100%;
60
-
61
- @media (--screen-md) {
62
- --CardContainer-padding-block: var(--size-16);
63
- --CardContainer-padding-inline: var(--size-16);
64
- }
65
-
66
- // Set through the padding variables, so the `inactive` spotlight's padding still composes.
67
- &--padding-none {
68
- --CardContainer-padding-block: 0;
69
- --CardContainer-padding-inline: 0;
70
-
71
- // Full-bleed content has to follow the card's rounded corners. `overflow` leaves the card's
72
- // own shadow alone, which `clip-path` would cut off.
73
- overflow: clip;
74
- }
75
-
76
- &--active {
77
- --CardContainer-background-color: var(--color-background-elevated);
78
- // The raised surface's shadow already separates it, so its border is a softer hairline.
79
- --CardContainer-border-color: color-mix(in srgb, var(--color-border-neutral) 25%, transparent);
80
-
81
- // The shadow is only designed for the light themes. Every theme root has `np-theme-personal`,
82
- // so a light theme is one without any of the dark or green theme classes.
83
- .np-theme-personal:not(
84
- .np-theme-personal--dark,
85
- .np-theme-personal--bright-green,
86
- .np-theme-personal--forest-green,
87
- .np-theme-business--dark,
88
- .np-theme-business--bright-green,
89
- .np-theme-business--forest-green,
90
- .np-theme-platform--forest-green
91
- )
92
- & {
93
- box-shadow: var(--CardContainer-shadow);
94
- }
95
- }
96
-
97
- &--inactive {
98
- --CardContainer-background-color: color-mix(
99
- in srgb,
100
- var(--color-background-neutral) 25%,
101
- transparent
102
- );
103
-
104
- // The dashed stroke is drawn inside the card instead of on a border, so the stroke isn't
105
- // clipped with `padding="none"`. The padding takes the border's 1px, so content stays put.
106
- border-width: 0;
107
- padding: calc(var(--CardContainer-padding-block) + 1px)
108
- calc(var(--CardContainer-padding-inline) + 1px);
109
- position: relative;
110
- }
111
-
112
- // An `interactive` card follows its direct child link or button, such as a link that fills the
113
- // card, so it only looks clickable where it can be clicked. Keyboard focus on the child shows the
114
- // hover state too. A card without a spotlight has no hover or pressed state.
115
- &--interactive:has(
116
- > a[href]:hover,
117
- > button:enabled:hover,
118
- > a[href]:focus-visible,
119
- > button:enabled:focus-visible
120
- ) {
121
- .wds-card-container-hover-state();
122
- }
123
-
124
- // Pressed state, while the same direct child is pressed. After the hover rule, so pressed wins
125
- // while the pointer is down.
126
- &--interactive:has(> a[href]:active, > button:enabled:active) {
127
- .wds-card-container-pressed-state();
128
- }
129
-
130
- // Same treatment as ListItem's inactive spotlight: CSS `dashed` borders have a browser-chosen
131
- // dash length, so the 6px/6px dashes are an SVG stroke along the card's edge.
132
- &__border {
133
- // A size container, so the stroke's corner radius can be capped by the card's height. An SVG
134
- // `rect` clamps `rx` and `ry` separately, which turns the corners of a card shorter than twice
135
- // the radius into ellipses, where `border-radius` keeps them round.
136
- container-type: size;
137
- inset: 0;
138
- pointer-events: none;
139
- position: absolute;
140
-
141
- svg {
142
- display: block;
143
- height: 100%;
144
- width: 100%;
145
- }
146
-
147
- rect {
148
- --CardContainer-border-dash-size: calc(var(--size-12) * 0.5);
149
- --CardContainer-border-stroke-radius: calc(
150
- min(var(--CardContainer-border-radius), 50cqh, 50cqw) - 0.5px
151
- );
152
-
153
- fill: none;
154
- height: calc(100% - 1px);
155
- rx: var(--CardContainer-border-stroke-radius);
156
- ry: var(--CardContainer-border-stroke-radius);
157
- stroke: var(--color-border-neutral);
158
- stroke-dasharray: var(--CardContainer-border-dash-size) var(--CardContainer-border-dash-size);
159
- width: calc(100% - 1px);
160
- x: 0.5px;
161
- y: 0.5px;
162
- }
163
- }
164
- }
@@ -1,150 +0,0 @@
1
- import { render, screen, userEvent } from '../test-utils';
2
- import CardContainer from './CardContainer';
3
- import type { CardContainerSpotlight } from './CardContainer.types';
4
-
5
- describe('CardContainer', () => {
6
- const content = 'Container content';
7
-
8
- describe('rendering', () => {
9
- it('renders children', () => {
10
- render(<CardContainer>{content}</CardContainer>);
11
- expect(screen.getByText(content)).toBeInTheDocument();
12
- });
13
-
14
- it('renders as a section by default', () => {
15
- render(<CardContainer data-testid="container">{content}</CardContainer>);
16
- expect(screen.getByTestId('container').tagName).toBe('SECTION');
17
- });
18
-
19
- it.each(['div', 'article'] as const)('renders as %s when as is provided', (as) => {
20
- render(
21
- <CardContainer as={as} data-testid="container">
22
- {content}
23
- </CardContainer>,
24
- );
25
- expect(screen.getByTestId('container').tagName).toBe(as.toUpperCase());
26
- });
27
-
28
- it('applies className to the root element', () => {
29
- render(
30
- <CardContainer className="custom-class" data-testid="container">
31
- {content}
32
- </CardContainer>,
33
- );
34
- expect(screen.getByTestId('container')).toHaveClass('wds-card-container', 'custom-class');
35
- });
36
-
37
- it('spreads unknown props onto the root element', () => {
38
- render(
39
- <CardContainer aria-label="Cards" data-testid="container">
40
- {content}
41
- </CardContainer>,
42
- );
43
- expect(screen.getByTestId('container')).toHaveAttribute('aria-label', 'Cards');
44
- });
45
- });
46
-
47
- describe('padding', () => {
48
- it('has no padding modifier by default', () => {
49
- render(<CardContainer data-testid="container">{content}</CardContainer>);
50
- expect(screen.getByTestId('container')).not.toHaveClass('wds-card-container--padding-none');
51
- });
52
-
53
- it('renders the padding-none modifier', () => {
54
- render(
55
- <CardContainer padding="none" data-testid="container">
56
- {content}
57
- </CardContainer>,
58
- );
59
- expect(screen.getByTestId('container')).toHaveClass('wds-card-container--padding-none');
60
- });
61
-
62
- it('keeps the dashed border for the inactive spotlight without padding', () => {
63
- render(
64
- <CardContainer padding="none" spotlight="inactive" data-testid="container">
65
- {content}
66
- </CardContainer>,
67
- );
68
- expect(
69
- screen.getByTestId('container').querySelector('.wds-card-container__border'),
70
- ).toBeInTheDocument();
71
- });
72
- });
73
-
74
- describe('spotlight', () => {
75
- it('has no spotlight modifier by default', () => {
76
- render(<CardContainer data-testid="container">{content}</CardContainer>);
77
- const root = screen.getByTestId('container');
78
- expect(root).not.toHaveClass('wds-card-container--active');
79
- expect(root).not.toHaveClass('wds-card-container--inactive');
80
- });
81
-
82
- it.each<CardContainerSpotlight>(['active', 'inactive'])(
83
- 'renders the %s spotlight modifier',
84
- (spotlight) => {
85
- render(
86
- <CardContainer spotlight={spotlight} data-testid="container">
87
- {content}
88
- </CardContainer>,
89
- );
90
- expect(screen.getByTestId('container')).toHaveClass(`wds-card-container--${spotlight}`);
91
- },
92
- );
93
-
94
- it('draws a decorative dashed border for the inactive spotlight', () => {
95
- render(
96
- <CardContainer spotlight="inactive" data-testid="container">
97
- {content}
98
- </CardContainer>,
99
- );
100
- expect(
101
- screen.getByTestId('container').querySelector('.wds-card-container__border'),
102
- ).toHaveAttribute('aria-hidden', 'true');
103
- });
104
-
105
- it.each<CardContainerSpotlight | undefined>(['active', undefined])(
106
- 'draws no dashed border for spotlight %s',
107
- (spotlight) => {
108
- render(
109
- <CardContainer spotlight={spotlight} data-testid="container">
110
- {content}
111
- </CardContainer>,
112
- );
113
- const root = screen.getByTestId('container');
114
- expect(root.querySelector('.wds-card-container__border')).not.toBeInTheDocument();
115
- },
116
- );
117
- });
118
-
119
- describe('interactive', () => {
120
- it('is not interactive by default', () => {
121
- render(<CardContainer data-testid="container">{content}</CardContainer>);
122
- expect(screen.getByTestId('container')).not.toHaveClass('wds-card-container--interactive');
123
- });
124
-
125
- it('adds the interactive modifier without changing the element', () => {
126
- render(
127
- <CardContainer interactive data-testid="container">
128
- <a href="/cards">{content}</a>
129
- </CardContainer>,
130
- );
131
- const root = screen.getByTestId('container');
132
- expect(root.tagName).toBe('SECTION');
133
- expect(root).toHaveClass('wds-card-container--interactive');
134
- expect(root).not.toHaveAttribute('interactive');
135
- });
136
-
137
- it('spreads onClick onto the root element as a plain handler', async () => {
138
- const onClick = jest.fn();
139
- render(
140
- <CardContainer data-testid="container" onClick={onClick}>
141
- {content}
142
- </CardContainer>,
143
- );
144
- const root = screen.getByTestId('container');
145
- expect(root.tagName).toBe('SECTION');
146
- await userEvent.click(root);
147
- expect(onClick).toHaveBeenCalledTimes(1);
148
- });
149
- });
150
- });