@transferwise/components 0.0.0-experimental-63c3fc6 → 0.0.0-experimental-5f03867

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 (147) hide show
  1. package/build/Snackbar/Snackbar.js +178 -102
  2. package/build/Snackbar/Snackbar.js.map +1 -1
  3. package/build/Snackbar/Snackbar.messages.js +14 -0
  4. package/build/Snackbar/Snackbar.messages.js.map +1 -0
  5. package/build/Snackbar/Snackbar.messages.mjs +10 -0
  6. package/build/Snackbar/Snackbar.messages.mjs.map +1 -0
  7. package/build/Snackbar/Snackbar.mjs +180 -101
  8. package/build/Snackbar/Snackbar.mjs.map +1 -1
  9. package/build/Snackbar/SnackbarContext.js.map +1 -1
  10. package/build/Snackbar/SnackbarContext.mjs.map +1 -1
  11. package/build/Snackbar/SnackbarPortal.js +12 -0
  12. package/build/Snackbar/SnackbarPortal.js.map +1 -0
  13. package/build/Snackbar/SnackbarPortal.mjs +8 -0
  14. package/build/Snackbar/SnackbarPortal.mjs.map +1 -0
  15. package/build/Snackbar/SnackbarProvider.js +24 -28
  16. package/build/Snackbar/SnackbarProvider.js.map +1 -1
  17. package/build/Snackbar/SnackbarProvider.mjs +25 -29
  18. package/build/Snackbar/SnackbarProvider.mjs.map +1 -1
  19. package/build/Snackbar/constants.js +17 -0
  20. package/build/Snackbar/constants.js.map +1 -0
  21. package/build/Snackbar/constants.mjs +11 -0
  22. package/build/Snackbar/constants.mjs.map +1 -0
  23. package/build/Snackbar/useSnackbarAutoDismiss.js +66 -0
  24. package/build/Snackbar/useSnackbarAutoDismiss.js.map +1 -0
  25. package/build/Snackbar/useSnackbarAutoDismiss.mjs +62 -0
  26. package/build/Snackbar/useSnackbarAutoDismiss.mjs.map +1 -0
  27. package/build/Snackbar/useSnackbarManager.js +153 -0
  28. package/build/Snackbar/useSnackbarManager.js.map +1 -0
  29. package/build/Snackbar/useSnackbarManager.mjs +149 -0
  30. package/build/Snackbar/useSnackbarManager.mjs.map +1 -0
  31. package/build/Snackbar/useSnackbarSwipeDismiss.js +135 -0
  32. package/build/Snackbar/useSnackbarSwipeDismiss.js.map +1 -0
  33. package/build/Snackbar/useSnackbarSwipeDismiss.mjs +131 -0
  34. package/build/Snackbar/useSnackbarSwipeDismiss.mjs.map +1 -0
  35. package/build/i18n/en.json +1 -0
  36. package/build/i18n/en.json.js +1 -0
  37. package/build/i18n/en.json.js.map +1 -1
  38. package/build/i18n/en.json.mjs +1 -0
  39. package/build/i18n/en.json.mjs.map +1 -1
  40. package/build/index.js +0 -4
  41. package/build/index.js.map +1 -1
  42. package/build/index.mjs +0 -2
  43. package/build/index.mjs.map +1 -1
  44. package/build/main.css +116 -191
  45. package/build/styles/Snackbar/Snackbar.css +117 -54
  46. package/build/styles/main.css +116 -191
  47. package/build/types/Snackbar/Snackbar.d.ts +55 -9
  48. package/build/types/Snackbar/Snackbar.d.ts.map +1 -1
  49. package/build/types/Snackbar/Snackbar.messages.d.ts +9 -0
  50. package/build/types/Snackbar/Snackbar.messages.d.ts.map +1 -0
  51. package/build/types/Snackbar/Snackbar.types.d.ts +12 -0
  52. package/build/types/Snackbar/Snackbar.types.d.ts.map +1 -0
  53. package/build/types/Snackbar/SnackbarContext.d.ts +2 -2
  54. package/build/types/Snackbar/SnackbarContext.d.ts.map +1 -1
  55. package/build/types/Snackbar/SnackbarPortal.d.ts +4 -0
  56. package/build/types/Snackbar/SnackbarPortal.d.ts.map +1 -0
  57. package/build/types/Snackbar/SnackbarProvider.d.ts +7 -0
  58. package/build/types/Snackbar/SnackbarProvider.d.ts.map +1 -1
  59. package/build/types/Snackbar/constants.d.ts +9 -0
  60. package/build/types/Snackbar/constants.d.ts.map +1 -0
  61. package/build/types/Snackbar/index.d.ts +1 -1
  62. package/build/types/Snackbar/index.d.ts.map +1 -1
  63. package/build/types/Snackbar/useSnackbar.d.ts +1 -1
  64. package/build/types/Snackbar/useSnackbar.d.ts.map +1 -1
  65. package/build/types/Snackbar/useSnackbarAutoDismiss.d.ts +13 -0
  66. package/build/types/Snackbar/useSnackbarAutoDismiss.d.ts.map +1 -0
  67. package/build/types/Snackbar/useSnackbarManager.d.ts +20 -0
  68. package/build/types/Snackbar/useSnackbarManager.d.ts.map +1 -0
  69. package/build/types/Snackbar/useSnackbarSwipeDismiss.d.ts +22 -0
  70. package/build/types/Snackbar/useSnackbarSwipeDismiss.d.ts.map +1 -0
  71. package/build/types/index.d.ts +1 -5
  72. package/build/types/index.d.ts.map +1 -1
  73. package/build/types/test-utils/index.d.ts +2 -0
  74. package/build/types/test-utils/index.d.ts.map +1 -1
  75. package/package.json +3 -3
  76. package/src/Snackbar/Snackbar.css +117 -54
  77. package/src/Snackbar/Snackbar.less +139 -60
  78. package/src/Snackbar/Snackbar.messages.ts +9 -0
  79. package/src/Snackbar/Snackbar.test.tsx +989 -119
  80. package/src/Snackbar/Snackbar.tsx +208 -93
  81. package/src/Snackbar/Snackbar.types.ts +17 -0
  82. package/src/Snackbar/SnackbarContext.ts +2 -2
  83. package/src/Snackbar/SnackbarPortal.tsx +8 -0
  84. package/src/Snackbar/SnackbarProvider.test.tsx +783 -0
  85. package/src/Snackbar/SnackbarProvider.tsx +27 -19
  86. package/src/Snackbar/_stories/Snackbar.accessibility.docs.mdx +66 -0
  87. package/src/Snackbar/_stories/Snackbar.migration.docs.mdx +64 -0
  88. package/src/Snackbar/_stories/Snackbar.rtl.test.story.tsx +16 -8
  89. package/src/Snackbar/_stories/Snackbar.story.tsx +389 -61
  90. package/src/Snackbar/_stories/Snackbar.test.story.tsx +171 -47
  91. package/src/Snackbar/constants.ts +10 -0
  92. package/src/Snackbar/index.ts +1 -1
  93. package/src/Snackbar/useSnackbarAutoDismiss.ts +74 -0
  94. package/src/Snackbar/useSnackbarManager.ts +169 -0
  95. package/src/Snackbar/useSnackbarSwipeDismiss.ts +169 -0
  96. package/src/i18n/en.json +1 -0
  97. package/src/index.ts +1 -14
  98. package/src/main.css +116 -191
  99. package/src/main.less +0 -2
  100. package/build/CardContainer/CardContainer.js +0 -33
  101. package/build/CardContainer/CardContainer.js.map +0 -1
  102. package/build/CardContainer/CardContainer.mjs +0 -29
  103. package/build/CardContainer/CardContainer.mjs.map +0 -1
  104. package/build/CardWidget/CardWidget.js +0 -94
  105. package/build/CardWidget/CardWidget.js.map +0 -1
  106. package/build/CardWidget/CardWidget.mjs +0 -90
  107. package/build/CardWidget/CardWidget.mjs.map +0 -1
  108. package/build/CardWidget/CardWidgetAction.js +0 -70
  109. package/build/CardWidget/CardWidgetAction.js.map +0 -1
  110. package/build/CardWidget/CardWidgetAction.mjs +0 -65
  111. package/build/CardWidget/CardWidgetAction.mjs.map +0 -1
  112. package/build/styles/CardContainer/CardContainer.css +0 -74
  113. package/build/styles/CardWidget/CardWidget.css +0 -64
  114. package/build/types/CardContainer/CardContainer.d.ts +0 -8
  115. package/build/types/CardContainer/CardContainer.d.ts.map +0 -1
  116. package/build/types/CardContainer/CardContainer.types.d.ts +0 -44
  117. package/build/types/CardContainer/CardContainer.types.d.ts.map +0 -1
  118. package/build/types/CardContainer/_stories/helpers.d.ts +0 -3
  119. package/build/types/CardContainer/_stories/helpers.d.ts.map +0 -1
  120. package/build/types/CardContainer/index.d.ts +0 -3
  121. package/build/types/CardContainer/index.d.ts.map +0 -1
  122. package/build/types/CardWidget/CardWidget.d.ts +0 -8
  123. package/build/types/CardWidget/CardWidget.d.ts.map +0 -1
  124. package/build/types/CardWidget/CardWidget.types.d.ts +0 -52
  125. package/build/types/CardWidget/CardWidget.types.d.ts.map +0 -1
  126. package/build/types/CardWidget/CardWidgetAction.d.ts +0 -54
  127. package/build/types/CardWidget/CardWidgetAction.d.ts.map +0 -1
  128. package/build/types/CardWidget/index.d.ts +0 -4
  129. package/build/types/CardWidget/index.d.ts.map +0 -1
  130. package/src/CardContainer/CardContainer.css +0 -74
  131. package/src/CardContainer/CardContainer.less +0 -112
  132. package/src/CardContainer/CardContainer.test.tsx +0 -118
  133. package/src/CardContainer/CardContainer.tsx +0 -39
  134. package/src/CardContainer/CardContainer.types.ts +0 -50
  135. package/src/CardContainer/_stories/CardContainer.story.tsx +0 -136
  136. package/src/CardContainer/_stories/CardContainer.test.story.tsx +0 -103
  137. package/src/CardContainer/_stories/helpers.tsx +0 -50
  138. package/src/CardContainer/index.ts +0 -6
  139. package/src/CardWidget/CardWidget.css +0 -64
  140. package/src/CardWidget/CardWidget.less +0 -82
  141. package/src/CardWidget/CardWidget.test.tsx +0 -189
  142. package/src/CardWidget/CardWidget.tsx +0 -74
  143. package/src/CardWidget/CardWidget.types.ts +0 -60
  144. package/src/CardWidget/CardWidgetAction.tsx +0 -126
  145. package/src/CardWidget/_stories/CardWidget.story.tsx +0 -209
  146. package/src/CardWidget/_stories/CardWidget.test.story.tsx +0 -350
  147. package/src/CardWidget/index.ts +0 -3
@@ -1,52 +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
- /** The widget always has the card's default padding, so its header and content line up. */
40
- export interface CardWidgetProps extends Omit<CardContainerProps, 'padding'> {
41
- /**
42
- * The widget's header: a title, optionally a link, and up to two actions.
43
- * @default undefined
44
- */
45
- header?: CardWidgetHeader;
46
- /**
47
- * The widget's content, rendered under the header.
48
- * @default undefined
49
- */
50
- children?: ReactNode;
51
- }
52
- //# 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,2FAA2F;AAC3F,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAC;IAC1E;;;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,74 +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__border {
52
- container-type: size;
53
- inset: 0;
54
- pointer-events: none;
55
- position: absolute;
56
- }
57
- .wds-card-container__border svg {
58
- display: block;
59
- block-size: 100%;
60
- inline-size: 100%;
61
- }
62
- .wds-card-container__border rect {
63
- --CardContainer-border-dash-size: calc(var(--size-12) * 0.5);
64
- --CardContainer-border-stroke-radius: calc(min(var(--CardContainer-border-radius), 50cqh, 50cqw) - 0.5px);
65
- fill: none;
66
- block-size: calc(100% - 1px);
67
- rx: var(--CardContainer-border-stroke-radius);
68
- ry: var(--CardContainer-border-stroke-radius);
69
- stroke: var(--color-border-neutral);
70
- stroke-dasharray: var(--CardContainer-border-dash-size) var(--CardContainer-border-dash-size);
71
- inline-size: calc(100% - 1px);
72
- x: 0.5px;
73
- y: 0.5px;
74
- }
@@ -1,112 +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: rgb(14 15 12 / 4%);
10
- --CardContainer-shadow:
11
- 0 2px 12px var(--CardContainer-shadow-color), 0 2px 32px var(--CardContainer-shadow-color);
12
- --CardContainer-background-color: var(--color-background-screen);
13
- --CardContainer-border-color: var(--color-border-neutral);
14
- --CardContainer-border-radius: var(--radius-large);
15
- --CardContainer-padding-block: var(--size-8);
16
- --CardContainer-padding-inline: var(--size-8);
17
-
18
- background-color: var(--CardContainer-background-color);
19
- border: 1px solid var(--CardContainer-border-color);
20
- border-radius: var(--CardContainer-border-radius);
21
- box-sizing: border-box;
22
- display: flex;
23
- flex-direction: column;
24
- padding: var(--CardContainer-padding-block) var(--CardContainer-padding-inline);
25
- width: 100%;
26
-
27
- @media (--screen-md) {
28
- --CardContainer-padding-block: var(--size-16);
29
- --CardContainer-padding-inline: var(--size-16);
30
- }
31
-
32
- // Set through the padding variables, so the `inactive` spotlight's padding still composes.
33
- &--padding-none {
34
- --CardContainer-padding-block: 0;
35
- --CardContainer-padding-inline: 0;
36
-
37
- // Full-bleed content has to follow the card's rounded corners. `overflow` leaves the card's
38
- // own shadow alone, which `clip-path` would cut off.
39
- overflow: clip;
40
- }
41
-
42
- &--active {
43
- --CardContainer-background-color: var(--color-background-elevated);
44
- // The raised surface's shadow already separates it, so its border is a softer hairline.
45
- --CardContainer-border-color: color-mix(in srgb, var(--color-border-neutral) 25%, transparent);
46
-
47
- // The shadow is only designed for the light themes. Every theme root has `np-theme-personal`,
48
- // so a light theme is one without any of the dark or green theme classes.
49
- .np-theme-personal:not(
50
- .np-theme-personal--dark,
51
- .np-theme-personal--bright-green,
52
- .np-theme-personal--forest-green,
53
- .np-theme-business--dark,
54
- .np-theme-business--bright-green,
55
- .np-theme-business--forest-green,
56
- .np-theme-platform--forest-green
57
- )
58
- & {
59
- box-shadow: var(--CardContainer-shadow);
60
- }
61
- }
62
-
63
- &--inactive {
64
- --CardContainer-background-color: color-mix(
65
- in srgb,
66
- var(--color-background-neutral) 25%,
67
- transparent
68
- );
69
-
70
- // The dashed stroke is drawn inside the card instead of on a border, so the stroke isn't
71
- // clipped with `padding="none"`. The padding takes the border's 1px, so content stays put.
72
- border-width: 0;
73
- padding: calc(var(--CardContainer-padding-block) + 1px)
74
- calc(var(--CardContainer-padding-inline) + 1px);
75
- position: relative;
76
- }
77
-
78
- // Same treatment as ListItem's inactive spotlight: CSS `dashed` borders have a browser-chosen
79
- // dash length, so the 6px/6px dashes are an SVG stroke along the card's edge.
80
- &__border {
81
- // A size container, so the stroke's corner radius can be capped by the card's height. An SVG
82
- // `rect` clamps `rx` and `ry` separately, which turns the corners of a card shorter than twice
83
- // the radius into ellipses, where `border-radius` keeps them round.
84
- container-type: size;
85
- inset: 0;
86
- pointer-events: none;
87
- position: absolute;
88
-
89
- svg {
90
- display: block;
91
- height: 100%;
92
- width: 100%;
93
- }
94
-
95
- rect {
96
- --CardContainer-border-dash-size: calc(var(--size-12) * 0.5);
97
- --CardContainer-border-stroke-radius: calc(
98
- min(var(--CardContainer-border-radius), 50cqh, 50cqw) - 0.5px
99
- );
100
-
101
- fill: none;
102
- height: calc(100% - 1px);
103
- rx: var(--CardContainer-border-stroke-radius);
104
- ry: var(--CardContainer-border-stroke-radius);
105
- stroke: var(--color-border-neutral);
106
- stroke-dasharray: var(--CardContainer-border-dash-size) var(--CardContainer-border-dash-size);
107
- width: calc(100% - 1px);
108
- x: 0.5px;
109
- y: 0.5px;
110
- }
111
- }
112
- }
@@ -1,118 +0,0 @@
1
- import { render, screen } 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
- });
@@ -1,39 +0,0 @@
1
- import { clsx } from 'clsx';
2
- import { forwardRef } from 'react';
3
-
4
- import type { CardContainerProps } from './CardContainer.types';
5
-
6
- /**
7
- * A bounded surface that groups related content. It has no slots of its own: children are stacked
8
- * with no gap between them. For a card with a title and actions, use `CardWidget`.
9
- */
10
- const CardContainer = forwardRef<HTMLElement, CardContainerProps>(
11
- ({ spotlight, padding = 'md', children, as: Element = 'section', className, ...props }, ref) => {
12
- return (
13
- <Element
14
- ref={ref as React.Ref<HTMLElement & HTMLDivElement>}
15
- className={clsx(
16
- 'wds-card-container',
17
- spotlight && `wds-card-container--${spotlight}`,
18
- padding === 'none' && 'wds-card-container--padding-none',
19
- className,
20
- )}
21
- {...props}
22
- >
23
- {children}
24
-
25
- {spotlight === 'inactive' && (
26
- <span aria-hidden="true" className="wds-card-container__border">
27
- <svg>
28
- <rect />
29
- </svg>
30
- </span>
31
- )}
32
- </Element>
33
- );
34
- },
35
- );
36
-
37
- CardContainer.displayName = 'CardContainer';
38
-
39
- export default CardContainer;
@@ -1,50 +0,0 @@
1
- import type { HTMLAttributes, ReactNode } from 'react';
2
-
3
- /**
4
- * Highlights the card, matching `ListItem`'s `spotlight`.
5
- *
6
- * - `active` — live content the user can act on. Raised, with a soft shadow and a hairline border.
7
- * - `inactive` — a feature the user has not set up yet. Recessed, with a dashed stroke.
8
- *
9
- * Without a spotlight the card is flat, with a solid stroke, for content that is present but not
10
- * actionable.
11
- */
12
- export type CardContainerSpotlight = 'active' | 'inactive';
13
-
14
- /**
15
- * - `md` — 8px, and 16px from the `md` breakpoint up.
16
- * - `none` — content runs to the card's edges.
17
- */
18
- export type CardContainerPadding = 'md' | 'none';
19
-
20
- export interface CardContainerProps extends HTMLAttributes<HTMLElement> {
21
- /**
22
- * Highlights the card as live content (`active`) or as a feature not set up yet (`inactive`).
23
- * Leave unset for a flat card.
24
- * @default undefined
25
- */
26
- spotlight?: CardContainerSpotlight;
27
-
28
- /**
29
- * Space between the card's edge and its content. `none` lets content such as images or charts
30
- * run to the card's edges, clipped to its rounded corners. Anything clipped at the edges
31
- * includes focus rings, so use `none` for non-interactive content, and keep lists on `md`.
32
- * @default 'md'
33
- */
34
- padding?: CardContainerPadding;
35
-
36
- /**
37
- * Content, stacked with no gap.
38
- * @default undefined
39
- */
40
- children?: ReactNode;
41
-
42
- /**
43
- * Element rendered as the root.
44
- * @default 'section'
45
- */
46
- as?: 'section' | 'div' | 'article';
47
-
48
- className?: string;
49
- 'data-testid'?: string;
50
- }
@@ -1,136 +0,0 @@
1
- import { Meta, StoryObj } from '@storybook/react-webpack5';
2
-
3
- import {
4
- createSandboxStory,
5
- globalScope,
6
- } from '../../../.storybook/components/sandbox/SandboxEditor';
7
- import Body from '../../Body';
8
- import CardContainer from '..';
9
- import type { CardContainerPadding, CardContainerSpotlight } from '..';
10
- import { CardsList, SampleContent } from './helpers';
11
-
12
- /**
13
- * A bounded surface that groups related content. It has no slots of its own: any content passed
14
- * as children is stacked with no gap, inside the card's padding.
15
- *
16
- * For a card with a title, a title link or actions, use `CardWidget`.
17
- */
18
- const meta: Meta<typeof CardContainer> = {
19
- component: CardContainer,
20
- title: 'Layouts/CardContainer',
21
- tags: ['early-access'],
22
- argTypes: {
23
- spotlight: {
24
- control: 'radio',
25
- options: [undefined, 'active', 'inactive'],
26
- table: {
27
- defaultValue: { summary: 'undefined' },
28
- type: { summary: 'CardContainerSpotlight' },
29
- },
30
- },
31
- padding: {
32
- control: 'radio',
33
- options: ['md', 'none'],
34
- table: { defaultValue: { summary: "'md'" }, type: { summary: 'CardContainerPadding' } },
35
- },
36
- as: {
37
- control: 'radio',
38
- options: ['section', 'div', 'article'],
39
- table: { defaultValue: { summary: "'section'" }, type: { summary: 'string' } },
40
- },
41
- children: {
42
- control: false,
43
- table: { type: { summary: 'ReactNode' } },
44
- },
45
- className: { control: 'text', table: { type: { summary: 'string' } } },
46
- 'data-testid': { control: 'text', table: { type: { summary: 'string' } } },
47
- },
48
- args: {
49
- spotlight: 'active',
50
- padding: 'md',
51
- as: 'section',
52
- },
53
- };
54
-
55
- export default meta;
56
-
57
- type Story = StoryObj<typeof CardContainer>;
58
-
59
- /** Experiment with the card's props. */
60
- export const Playground: Story = {
61
- render: (args) => (
62
- <CardContainer {...args}>
63
- <SampleContent />
64
- </CardContainer>
65
- ),
66
- };
67
-
68
- export const Sandbox = createSandboxStory({
69
- code: `<CardContainer spotlight="active">
70
- <Body>Your virtual card is ready to use.</Body>
71
- <Body>Spend in 40+ currencies with the real exchange rate.</Body>
72
- </CardContainer>`,
73
- scope: { ...globalScope, CardContainer },
74
- });
75
-
76
- /**
77
- * `spotlight` matches `ListItem`'s: `active` is raised, for live content, and `inactive` is
78
- * recessed with a dashed stroke, for a feature not set up yet. Without a spotlight the card is
79
- * flat.
80
- */
81
- export const Spotlight: Story = {
82
- parameters: { controls: { disable: true } },
83
- render: () => (
84
- <div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '480px' }}>
85
- {([undefined, 'active', 'inactive'] as (CardContainerSpotlight | undefined)[]).map(
86
- (spotlight) => (
87
- <CardContainer key={spotlight ?? 'none'} spotlight={spotlight}>
88
- <Body>Spotlight: {spotlight ?? 'not set'}</Body>
89
- </CardContainer>
90
- ),
91
- )}
92
- </div>
93
- ),
94
- };
95
-
96
- /**
97
- * `padding="md"` insets the content by 8px, and by 16px from the `md` breakpoint up.
98
- * `padding="none"` lets content such as images or charts run to the card's edges, clipped to its
99
- * rounded corners. The grey boxes fill the content area, so their edges show where the padding
100
- * ends.
101
- *
102
- * Focus rings are clipped at the edges too, so keep `none` for non-interactive content, and lists
103
- * on `md`.
104
- */
105
- export const Padding: Story = {
106
- parameters: { controls: { disable: true } },
107
- render: () => (
108
- <div style={{ display: 'flex', flexDirection: 'column', gap: '24px', maxWidth: '480px' }}>
109
- {(['md', 'none'] as CardContainerPadding[]).map((padding) =>
110
- ([undefined, 'active', 'inactive'] as (CardContainerSpotlight | undefined)[]).map(
111
- (spotlight) => (
112
- <CardContainer
113
- key={`${padding}-${spotlight ?? 'none'}`}
114
- padding={padding}
115
- spotlight={spotlight}
116
- >
117
- <div
118
- style={{ height: '120px', background: 'var(--color-background-screen-active)' }}
119
- />
120
- </CardContainer>
121
- ),
122
- ),
123
- )}
124
- </div>
125
- ),
126
- };
127
-
128
- /** A `List` placed directly in the card. */
129
- export const WithList: Story = {
130
- parameters: { controls: { disable: true } },
131
- render: () => (
132
- <CardContainer>
133
- <CardsList />
134
- </CardContainer>
135
- ),
136
- };