@transferwise/components 46.157.0 → 46.158.0

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 (50) hide show
  1. package/build/Markup/Markup.js +15 -14
  2. package/build/Markup/Markup.js.map +1 -1
  3. package/build/Markup/Markup.mjs +15 -14
  4. package/build/Markup/Markup.mjs.map +1 -1
  5. package/build/Popover/Popover.js +4 -0
  6. package/build/Popover/Popover.js.map +1 -1
  7. package/build/Popover/Popover.mjs +4 -0
  8. package/build/Popover/Popover.mjs.map +1 -1
  9. package/build/common/Panel/Panel.js +13 -4
  10. package/build/common/Panel/Panel.js.map +1 -1
  11. package/build/common/Panel/Panel.mjs +14 -5
  12. package/build/common/Panel/Panel.mjs.map +1 -1
  13. package/build/common/PropsValues/position.js +29 -0
  14. package/build/common/PropsValues/position.js.map +1 -1
  15. package/build/common/PropsValues/position.mjs +28 -1
  16. package/build/common/PropsValues/position.mjs.map +1 -1
  17. package/build/main.css +14 -0
  18. package/build/styles/Typeahead/Typeahead.css +10 -0
  19. package/build/styles/main.css +14 -0
  20. package/build/types/Markup/Markup.d.ts.map +1 -1
  21. package/build/types/Markup/Markup.types.d.ts +16 -5
  22. package/build/types/Markup/Markup.types.d.ts.map +1 -1
  23. package/build/types/Markup/index.d.ts +1 -1
  24. package/build/types/Markup/index.d.ts.map +1 -1
  25. package/build/types/Popover/Popover.d.ts +1 -1
  26. package/build/types/Popover/Popover.d.ts.map +1 -1
  27. package/build/types/common/Panel/Panel.d.ts +6 -6
  28. package/build/types/common/Panel/Panel.d.ts.map +1 -1
  29. package/build/types/common/PropsValues/position.d.ts +18 -1
  30. package/build/types/common/PropsValues/position.d.ts.map +1 -1
  31. package/build/types/common/ResponsivePanel/ResponsivePanel.d.ts +2 -2
  32. package/build/types/index.d.ts +1 -1
  33. package/build/types/index.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/Markup/Markup.test.tsx +8 -7
  36. package/src/Markup/Markup.tsx +18 -15
  37. package/src/Markup/Markup.types.ts +16 -6
  38. package/src/Markup/_stories/Markup.docs.mdx +60 -0
  39. package/src/Markup/_stories/Markup.story.tsx +7 -8
  40. package/src/Markup/index.ts +6 -1
  41. package/src/Popover/Popover.tsx +9 -2
  42. package/src/Typeahead/Typeahead.css +10 -0
  43. package/src/Typeahead/Typeahead.less +6 -0
  44. package/src/common/Panel/Panel.test.tsx +17 -0
  45. package/src/common/Panel/Panel.tsx +17 -8
  46. package/src/common/Panel/_stories/Panel.story.tsx +111 -0
  47. package/src/common/PropsValues/position.ts +54 -0
  48. package/src/i18n/en.json +1 -0
  49. package/src/index.ts +1 -1
  50. package/src/main.css +14 -0
@@ -1,7 +1,5 @@
1
1
  import React from 'react';
2
2
 
3
- import type { LinkProps } from '../Link';
4
-
5
3
  export type MarkupNode =
6
4
  | { type: 'text'; content: string }
7
5
  | { type: 'newline' }
@@ -20,9 +18,20 @@ export type MarkupNode =
20
18
  children: MarkupNode[];
21
19
  };
22
20
 
23
- export type MarkupActionHandler = NonNullable<LinkProps['onClick']>;
21
+ /**
22
+ * Callback signature for the `onAction` prop. Receives the action name from
23
+ * `<link action="name">` when clicked.
24
+ */
25
+ export type MarkupActionHandler = (actionId: string) => void;
24
26
 
25
- export type MarkupActions = Record<string, MarkupActionHandler>;
27
+ /**
28
+ * Per-prop Markup configuration that host components can accept via a `markupOptions`
29
+ * prop keyed by their text prop names (e.g. `{ title?: MarkupTextOptions; description?: MarkupTextOptions }`).
30
+ */
31
+ export type MarkupTextOptions = {
32
+ data?: Record<string, string | number>;
33
+ allowLinks?: 'internal' | 'all' | 'none';
34
+ };
26
35
 
27
36
  export interface MarkupProps {
28
37
  /**
@@ -45,9 +54,10 @@ export interface MarkupProps {
45
54
  * */
46
55
  allowLinks?: 'internal' | 'all' | 'none';
47
56
  /**
48
- * Map of action names to click handlers for `<link action="name">` button-mode links.
57
+ * Callback invoked when a `<link action="name">` button-mode link is clicked.
58
+ * Receives the action name as argument.
49
59
  * */
50
- actions?: MarkupActions;
60
+ onAction?: MarkupActionHandler;
51
61
  /**
52
62
  * Wrapper element. Defaults to `div` when the parsed tree contains `<paragraph>` nodes,
53
63
  * `span` otherwise. Set explicitly to override auto-detection.
@@ -4,6 +4,66 @@ import { Meta, Source } from '@storybook/addon-docs/blocks';
4
4
 
5
5
  # Developer notes
6
6
 
7
+ ## Integrating Markup in host components
8
+
9
+ To integrate any component with Markup (e.g. `title`, `description`), follow this pattern:
10
+
11
+ 1. **Add `onMarkupAction`** — if the component is expected to accept actions, add a single callback prop that the
12
+ component forwards to every internal `<Markup onAction={onMarkupAction}>` instance. The name of this prop must
13
+ remain consistent across components.
14
+
15
+ 2. **Add `markupOptions`** — an optional prop keyed by the text prop names, carrying per-prop Markup configuration
16
+ (`data`, `allowLinks`). `MarkupTextOptions` type is exposed by the component.
17
+
18
+ <Source dark language="tsx" code={`
19
+ import Markup, { type MarkupActionHandler, type MarkupTextOptions } from '../Markup';
20
+
21
+ type MyComponentProps = {
22
+ title: ReactNode;
23
+ description?: ReactNode;
24
+ onMarkupAction?: MarkupActionHandler;
25
+ markupOptions?: {
26
+ title?: MarkupTextOptions;
27
+ description?: MarkupTextOptions;
28
+ };
29
+ };
30
+
31
+ function MyComponent({ title, description, onMarkupAction, markupOptions }: MyComponentProps) {
32
+ return (
33
+ <div>
34
+ <Markup onAction={onMarkupAction} {...markupOptions?.title}>
35
+ {title}
36
+ </Markup>
37
+
38
+ {description && (
39
+ <Markup onAction={onMarkupAction} {...markupOptions?.description}>
40
+ {description}
41
+ </Markup>
42
+ )}
43
+ </div>
44
+ );
45
+ }
46
+ `} />
47
+
48
+ Consumer usage:
49
+
50
+ <Source
51
+ dark
52
+ language="tsx"
53
+ code={`
54
+ <MyComponent
55
+ title='Something went wrong. <link action="retry">Try again</link>.'
56
+ description='Your balance is {{amount}}.'
57
+ onMarkupAction={(actionId) => {
58
+ if (actionId === 'retry') refetch();
59
+ }}
60
+ markupOptions={{
61
+ description: { data: { amount: '£100.00' }, allowLinks: 'all' }
62
+ }}
63
+ />
64
+ `}
65
+ />
66
+
7
67
  ## How parsing works
8
68
 
9
69
  The parser runs in two phases: **tokenise** then **parse**.
@@ -1,6 +1,5 @@
1
1
  import React, { useState } from 'react';
2
2
  import { Meta, StoryObj } from '@storybook/react-webpack5';
3
- import { action } from 'storybook/actions';
4
3
 
5
4
  import {
6
5
  createSandboxStory,
@@ -9,7 +8,6 @@ import {
9
8
  import { storySourceWithoutNoise } from '../../../.storybook/helpers';
10
9
 
11
10
  import { Markup } from '../Markup';
12
- import type { MarkupActionHandler } from '../Markup.types';
13
11
 
14
12
  export default {
15
13
  component: Markup,
@@ -58,8 +56,8 @@ export const WithLinks: Story = {
58
56
  };
59
57
 
60
58
  /**
61
- * Button-mode link using the `action` attribute. The handler is provided via the `actions` prop.
62
- * If the named action is missing from the map, the link degrades to plain text.
59
+ * Button-mode link using the `action` attribute. The handler is provided via the `onAction` prop.
60
+ * If `onAction` is not provided, the link degrades to plain text.
63
61
  * */
64
62
  export const WithLinksAsButtons = storySourceWithoutNoise<Story>({
65
63
  args: {
@@ -68,14 +66,15 @@ export const WithLinksAsButtons = storySourceWithoutNoise<Story>({
68
66
  render: function Render(args) {
69
67
  const [count, setCount] = useState(0);
70
68
 
71
- const increment: MarkupActionHandler = (event) => {
72
- action('Button event')(event);
73
- setCount((prev) => prev + 1);
69
+ const handleAction = (actionId: string) => {
70
+ if (actionId === 'increment') {
71
+ setCount((prev) => prev + 1);
72
+ }
74
73
  };
75
74
 
76
75
  return (
77
76
  <>
78
- <Markup actions={{ increment }}>{args.children}</Markup>
77
+ <Markup onAction={handleAction}>{args.children}</Markup>
79
78
  <output>{count}</output>
80
79
  </>
81
80
  );
@@ -1,2 +1,7 @@
1
1
  export { default } from './Markup';
2
- export type { MarkupActionHandler, MarkupActions, MarkupNode, MarkupProps } from './Markup.types';
2
+ export type {
3
+ MarkupActionHandler,
4
+ MarkupNode,
5
+ MarkupProps,
6
+ MarkupTextOptions,
7
+ } from './Markup.types';
@@ -1,4 +1,3 @@
1
- import { useTheme } from '@wise/components-theming';
2
1
  import { clsx } from 'clsx';
3
2
  import { useRef, useState, cloneElement, useEffect, isValidElement, useId } from 'react';
4
3
 
@@ -9,10 +8,14 @@ import { logActionRequired } from '../utilities';
9
8
 
10
9
  /** @deprecated Use `"top" | "bottom"` instead. */
11
10
  type PopoverPreferredPlacementDeprecated =
11
+ | `${Position.TOP_LEFT}`
12
+ | `${Position.TOP_RIGHT}`
12
13
  | `${Position.LEFT_TOP}`
13
14
  | `${Position.RIGHT_TOP}`
14
15
  | `${Position.BOTTOM_RIGHT}`
15
- | `${Position.BOTTOM_LEFT}`;
16
+ | `${Position.BOTTOM_LEFT}`
17
+ | `${Position.LEFT_BOTTOM}`
18
+ | `${Position.RIGHT_BOTTOM}`;
16
19
 
17
20
  export type PopoverPreferredPlacement =
18
21
  | `${Position.TOP}`
@@ -35,11 +38,15 @@ export interface PopoverProps {
35
38
 
36
39
  function resolvePlacement(preferredPlacement: PopoverPreferredPlacement) {
37
40
  switch (preferredPlacement) {
41
+ case 'top-left':
42
+ case 'top-right':
38
43
  case 'left-top':
39
44
  case 'right-top':
40
45
  return 'top';
41
46
  case 'bottom-left':
42
47
  case 'bottom-right':
48
+ case 'left-bottom':
49
+ case 'right-bottom':
43
50
  return 'bottom';
44
51
  default:
45
52
  return preferredPlacement;
@@ -7,10 +7,20 @@
7
7
  .typeahead .dropdown-menu {
8
8
  max-width: 100%;
9
9
  width: 100%;
10
+ margin-top: 8px;
11
+ margin-top: var(--size-8);
10
12
  scroll-padding-top: 8px;
11
13
  scroll-padding-top: var(--size-8);
12
14
  scroll-padding-bottom: 8px;
13
15
  scroll-padding-bottom: var(--size-8);
16
+ --ring-outline-offset: calc(-1 * var(--ring-outline-width));
17
+ }
18
+ .typeahead .dropdown-menu:focus {
19
+ outline: none;
20
+ }
21
+ .typeahead .dropdown-menu:focus-visible {
22
+ outline: var(--ring-outline-color) solid var(--ring-outline-width);
23
+ outline-offset: var(--ring-outline-offset);
14
24
  }
15
25
  .np-theme-personal--forest-green .typeahead .dropdown-menu,
16
26
  .np-theme-personal--bright-green .typeahead .dropdown-menu,
@@ -1,5 +1,6 @@
1
1
  @import (reference) "./../styles/variables/legacy-variables.less";
2
2
  @import (reference) "./../styles/less/mixins/_logical-properties.less";
3
+ @import (reference) "./../styles/less/ring.less";
3
4
 
4
5
  @animation-timing-function: ease-in-out;
5
6
  @animation-duration: 0.03s;
@@ -18,8 +19,13 @@
18
19
  .dropdown-menu {
19
20
  max-width: 100%;
20
21
  width: 100%;
22
+ margin-top: var(--size-8);
21
23
  scroll-padding-top: var(--size-8);
22
24
  scroll-padding-bottom: var(--size-8);
25
+
26
+ .focus-ring();
27
+ --ring-outline-offset: calc(-1 * var(--ring-outline-width));
28
+
23
29
  .np-theme-personal--forest-green &,
24
30
  .np-theme-personal--bright-green &,
25
31
  .np-theme-personal--dark & {
@@ -76,4 +76,21 @@ describe('Panel', () => {
76
76
 
77
77
  const getPanel = () => document.querySelector('.np-panel--open');
78
78
  const getArrow = () => document.querySelector('.np-panel__arrow');
79
+
80
+ describe('aligned placements', () => {
81
+ it.each<[PanelProps['position'], string]>([
82
+ [Position.TOP_LEFT, 'top-start'],
83
+ [Position.TOP_RIGHT, 'top-end'],
84
+ [Position.RIGHT_TOP, 'right-start'],
85
+ [Position.RIGHT_BOTTOM, 'right-end'],
86
+ [Position.BOTTOM_RIGHT, 'bottom-end'],
87
+ [Position.BOTTOM_LEFT, 'bottom-start'],
88
+ [Position.LEFT_BOTTOM, 'left-end'],
89
+ [Position.LEFT_TOP, 'left-start'],
90
+ ])('converts %s to floating-ui placement %s', (position, expectedPlacement) => {
91
+ render(<Panel {...props} position={position} />);
92
+
93
+ expect(getPanel()).toHaveAttribute('data-popper-placement', expectedPlacement);
94
+ });
95
+ });
79
96
  });
@@ -12,15 +12,15 @@ import { clsx } from 'clsx';
12
12
  import {
13
13
  CSSProperties,
14
14
  HTMLAttributes,
15
- MutableRefObject,
16
15
  PropsWithChildren,
16
+ RefObject,
17
17
  SyntheticEvent,
18
18
  forwardRef,
19
19
  useContext,
20
20
  useRef,
21
21
  } from 'react';
22
22
 
23
- import { Position, PositionBottom, PositionLeft, PositionRight, PositionTop } from '..';
23
+ import { Position, type FloatingPlacement, positionToPlacement } from '..';
24
24
  import Dimmer from '../../Dimmer';
25
25
  import { OverlayIdContext } from '../../Provider/overlay/OverlayIdProvider';
26
26
 
@@ -29,11 +29,19 @@ const POPOVER_OFFSET = 16;
29
29
  // By default the flip positioning explores only the opposite alternative. So if left is passed and there's no enough space
30
30
  // the right one gets chosen. If there's no space on both sides popover goes back to the initially chosen one left.
31
31
  // This mapping forces popover to try the four available positions before going back to the initial chosen one.
32
- const fallbackPlacements: Record<string, Placement[]> = {
32
+ const fallbackPlacements: Record<Placement, Placement[]> = {
33
33
  [Position.TOP]: [Position.BOTTOM, Position.RIGHT, Position.LEFT],
34
34
  [Position.BOTTOM]: [Position.TOP, Position.RIGHT, Position.LEFT],
35
35
  [Position.LEFT]: [Position.RIGHT, Position.TOP, Position.BOTTOM],
36
36
  [Position.RIGHT]: [Position.LEFT, Position.TOP, Position.BOTTOM],
37
+ 'top-start': ['bottom-start', 'top-end', 'bottom-end'],
38
+ 'top-end': ['bottom-end', 'top-start', 'bottom-start'],
39
+ 'bottom-start': ['top-start', 'bottom-end', 'top-end'],
40
+ 'bottom-end': ['top-end', 'bottom-start', 'top-start'],
41
+ 'left-start': ['right-start', 'left-end', 'right-end'],
42
+ 'left-end': ['right-end', 'left-start', 'right-start'],
43
+ 'right-start': ['left-start', 'right-end', 'left-end'],
44
+ 'right-end': ['left-end', 'right-start', 'left-start'],
37
45
  };
38
46
 
39
47
  export type PanelProps = PropsWithChildren<{
@@ -42,8 +50,8 @@ export type PanelProps = PropsWithChildren<{
42
50
  altAxis?: boolean;
43
51
  open?: boolean;
44
52
  onClose?: (event: Event | SyntheticEvent) => void;
45
- position?: PositionBottom | PositionLeft | PositionRight | PositionTop;
46
- anchorRef: MutableRefObject<Element | null>;
53
+ position?: FloatingPlacement;
54
+ anchorRef: RefObject<Element | null>;
47
55
  anchorWidth?: boolean;
48
56
  considerHeight?: boolean;
49
57
  }> &
@@ -66,6 +74,7 @@ const Panel = forwardRef<HTMLDivElement, PanelProps>(function Panel(
66
74
  reference,
67
75
  ) {
68
76
  const arrowRef = useRef<HTMLDivElement | null>(null);
77
+ const floatingPlacement = positionToPlacement(position);
69
78
 
70
79
  const middleware = [];
71
80
 
@@ -73,10 +82,10 @@ const Panel = forwardRef<HTMLDivElement, PanelProps>(function Panel(
73
82
  middleware.push(offset(POPOVER_OFFSET));
74
83
  }
75
84
 
76
- if (flip && fallbackPlacements[position]) {
85
+ if (flip && fallbackPlacements[floatingPlacement]) {
77
86
  middleware.push(
78
87
  flipMiddleware({
79
- fallbackPlacements: fallbackPlacements[position],
88
+ fallbackPlacements: fallbackPlacements[floatingPlacement],
80
89
  }),
81
90
  );
82
91
  }
@@ -104,7 +113,7 @@ const Panel = forwardRef<HTMLDivElement, PanelProps>(function Panel(
104
113
  }
105
114
 
106
115
  const { refs, floatingStyles, placement, middlewareData } = useFloating({
107
- placement: position,
116
+ placement: floatingPlacement,
108
117
  middleware,
109
118
  elements: {
110
119
  reference: anchorRef.current,
@@ -0,0 +1,111 @@
1
+ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import { useRef, useState } from 'react';
3
+
4
+ import Button from '../../../Button';
5
+ import { Position } from '../../PropsValues/position';
6
+ import Panel from '../Panel';
7
+
8
+ export default {
9
+ component: Panel,
10
+ title: 'Dialogs/Panel',
11
+ parameters: {
12
+ docs: {
13
+ description: {
14
+ component:
15
+ 'Panel is a low-level floating component used by Popover and ResponsivePanel. It supports all floating-ui placements.',
16
+ },
17
+ },
18
+ },
19
+ } satisfies Meta<typeof Panel>;
20
+
21
+ type Story = StoryObj<typeof Panel>;
22
+
23
+ const PanelDemo = ({
24
+ position,
25
+ label,
26
+ }: {
27
+ position: Parameters<typeof Panel>[0]['position'];
28
+ label: string;
29
+ }) => {
30
+ const anchorRef = useRef<HTMLButtonElement>(null);
31
+ const [open, setOpen] = useState(false);
32
+
33
+ return (
34
+ <div style={{ display: 'inline-block' }}>
35
+ <Button ref={anchorRef} onClick={() => setOpen((prev) => !prev)}>
36
+ {label}
37
+ </Button>
38
+ <Panel
39
+ open={open}
40
+ position={position}
41
+ anchorRef={anchorRef}
42
+ onClose={() => setOpen(false)}
43
+ aria-label={`Panel positioned ${label}`}
44
+ >
45
+ <div style={{ padding: '16px', maxWidth: '200px' }}>
46
+ Panel content positioned at <strong>{label}</strong>
47
+ </div>
48
+ </Panel>
49
+ </div>
50
+ );
51
+ };
52
+
53
+ export const CardinalPositions: Story = {
54
+ render: () => (
55
+ <div
56
+ style={{
57
+ display: 'flex',
58
+ flexWrap: 'wrap',
59
+ gap: '48px',
60
+ justifyContent: 'center',
61
+ alignItems: 'center',
62
+ padding: '120px 80px',
63
+ }}
64
+ >
65
+ <PanelDemo position={Position.TOP} label="top" />
66
+ <PanelDemo position={Position.RIGHT} label="right" />
67
+ <PanelDemo position={Position.BOTTOM} label="bottom" />
68
+ <PanelDemo position={Position.LEFT} label="left" />
69
+ </div>
70
+ ),
71
+ };
72
+
73
+ export const VerticalAlignedPositions: Story = {
74
+ render: () => (
75
+ <div
76
+ style={{
77
+ display: 'flex',
78
+ flexWrap: 'wrap',
79
+ gap: '48px',
80
+ justifyContent: 'center',
81
+ alignItems: 'center',
82
+ padding: '120px 80px',
83
+ }}
84
+ >
85
+ <PanelDemo position={Position.TOP_LEFT} label="top-left" />
86
+ <PanelDemo position={Position.TOP_RIGHT} label="top-right" />
87
+ <PanelDemo position={Position.BOTTOM_LEFT} label="bottom-left" />
88
+ <PanelDemo position={Position.BOTTOM_RIGHT} label="bottom-right" />
89
+ </div>
90
+ ),
91
+ };
92
+
93
+ export const HorizontalAlignedPositions: Story = {
94
+ render: () => (
95
+ <div
96
+ style={{
97
+ display: 'flex',
98
+ flexWrap: 'wrap',
99
+ gap: '48px',
100
+ justifyContent: 'center',
101
+ alignItems: 'center',
102
+ padding: '120px 80px',
103
+ }}
104
+ >
105
+ <PanelDemo position={Position.LEFT_TOP} label="left-top" />
106
+ <PanelDemo position={Position.LEFT_BOTTOM} label="left-bottom" />
107
+ <PanelDemo position={Position.RIGHT_TOP} label="right-top" />
108
+ <PanelDemo position={Position.RIGHT_BOTTOM} label="right-bottom" />
109
+ </div>
110
+ ),
111
+ };
@@ -1,13 +1,19 @@
1
+ import type { Placement } from '@floating-ui/react';
2
+
1
3
  export enum Position {
2
4
  BOTTOM = 'bottom',
3
5
  BOTTOM_LEFT = 'bottom-left',
4
6
  BOTTOM_RIGHT = 'bottom-right',
5
7
  CENTER = 'center',
6
8
  LEFT = 'left',
9
+ LEFT_BOTTOM = 'left-bottom',
7
10
  LEFT_TOP = 'left-top',
8
11
  RIGHT = 'right',
12
+ RIGHT_BOTTOM = 'right-bottom',
9
13
  RIGHT_TOP = 'right-top',
10
14
  TOP = 'top',
15
+ TOP_LEFT = 'top-left',
16
+ TOP_RIGHT = 'top-right',
11
17
  }
12
18
 
13
19
  export type PositionCenter = 'center';
@@ -15,3 +21,51 @@ export type PositionTop = 'top';
15
21
  export type PositionRight = 'right';
16
22
  export type PositionBottom = 'bottom';
17
23
  export type PositionLeft = 'left';
24
+
25
+ export type PositionTopLeft = 'top-left';
26
+ export type PositionTopRight = 'top-right';
27
+ export type PositionBottomLeft = 'bottom-left';
28
+ export type PositionBottomRight = 'bottom-right';
29
+ export type PositionLeftTop = 'left-top';
30
+ export type PositionLeftBottom = 'left-bottom';
31
+ export type PositionRightTop = 'right-top';
32
+ export type PositionRightBottom = 'right-bottom';
33
+
34
+ /** Neptune position values that can be converted to floating-ui Placement */
35
+ export type FloatingPlacement =
36
+ | PositionTop
37
+ | PositionRight
38
+ | PositionBottom
39
+ | PositionLeft
40
+ | PositionTopLeft
41
+ | PositionTopRight
42
+ | PositionRightTop
43
+ | PositionRightBottom
44
+ | PositionBottomLeft
45
+ | PositionBottomRight
46
+ | PositionLeftTop
47
+ | PositionLeftBottom;
48
+
49
+ /** Maps Neptune position values to floating-ui Placement */
50
+ export const positionToPlacement = (position: FloatingPlacement): Placement => {
51
+ switch (position) {
52
+ case 'top-left':
53
+ return 'top-start';
54
+ case 'top-right':
55
+ return 'top-end';
56
+ case 'bottom-left':
57
+ return 'bottom-start';
58
+ case 'bottom-right':
59
+ return 'bottom-end';
60
+ case 'left-top':
61
+ return 'left-start';
62
+ case 'left-bottom':
63
+ return 'left-end';
64
+ case 'right-top':
65
+ return 'right-start';
66
+ case 'right-bottom':
67
+ return 'right-end';
68
+ default:
69
+ return position;
70
+ }
71
+ };
package/src/i18n/en.json CHANGED
@@ -50,6 +50,7 @@
50
50
  "neptune.Table.loaded": "Table data has loaded",
51
51
  "neptune.Table.loading": "Table data is loading",
52
52
  "neptune.Table.refreshPage": "Refresh page",
53
+ "neptune.Typeahead.suggestionsLabel": "Suggestions",
53
54
  "neptune.Upload.csButtonText": "Upload another file?",
54
55
  "neptune.Upload.csFailureText": "Upload failed. Please, try again",
55
56
  "neptune.Upload.csSuccessText": "Upload complete!",
package/src/index.ts CHANGED
@@ -95,7 +95,7 @@ export type { LegacyListItemProps } from './_deprecated/LegacyListItem';
95
95
  export type { LinkProps } from './Link';
96
96
  export type { LoaderProps } from './Loader';
97
97
  export type { MarkdownProps } from './Markdown';
98
- export type { MarkupActionHandler, MarkupActions, MarkupNode, MarkupProps } from './Markup';
98
+ export type { MarkupActionHandler, MarkupNode, MarkupProps, MarkupTextOptions } from './Markup';
99
99
  export type { ModalProps } from './Modal';
100
100
  export type { MoneyProps } from './Money';
101
101
  export type {
package/src/main.css CHANGED
@@ -34202,10 +34202,24 @@ html:not([dir="rtl"]) .np-navigation-option {
34202
34202
  .typeahead .dropdown-menu {
34203
34203
  max-width: 100%;
34204
34204
  width: 100%;
34205
+ margin-top: 8px;
34206
+ margin-top: var(--size-8);
34205
34207
  scroll-padding-top: 8px;
34206
34208
  scroll-padding-top: var(--size-8);
34207
34209
  scroll-padding-bottom: 8px;
34208
34210
  scroll-padding-bottom: var(--size-8);
34211
+ --ring-outline-offset: calc(-1 * var(--ring-outline-width));
34212
+ }
34213
+
34214
+ .typeahead .dropdown-menu:focus {
34215
+ outline: none;
34216
+ }
34217
+
34218
+ .typeahead .dropdown-menu:focus-visible {
34219
+ outline: #37517e solid 2px;
34220
+ outline: var(--ring-outline-color) solid var(--ring-outline-width);
34221
+ outline-offset: 2px;
34222
+ outline-offset: var(--ring-outline-offset);
34209
34223
  }
34210
34224
 
34211
34225
  .np-theme-personal--forest-green .typeahead .dropdown-menu,