@transferwise/components 0.0.0-experimental-a7b7094 → 0.0.0-experimental-580125e

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 (54) hide show
  1. package/build/field/Field.js +32 -63
  2. package/build/field/Field.js.map +1 -1
  3. package/build/field/Field.mjs +34 -65
  4. package/build/field/Field.mjs.map +1 -1
  5. package/build/i18n/en.json +0 -1
  6. package/build/i18n/en.json.js +0 -1
  7. package/build/i18n/en.json.js.map +1 -1
  8. package/build/i18n/en.json.mjs +0 -1
  9. package/build/i18n/en.json.mjs.map +1 -1
  10. package/build/inputs/TextArea.js +0 -25
  11. package/build/inputs/TextArea.js.map +1 -1
  12. package/build/inputs/TextArea.mjs +2 -27
  13. package/build/inputs/TextArea.mjs.map +1 -1
  14. package/build/inputs/contexts.js +0 -16
  15. package/build/inputs/contexts.js.map +1 -1
  16. package/build/inputs/contexts.mjs +2 -16
  17. package/build/inputs/contexts.mjs.map +1 -1
  18. package/build/main.css +3 -21
  19. package/build/popover/Popover.js +9 -12
  20. package/build/popover/Popover.js.map +1 -1
  21. package/build/popover/Popover.mjs +9 -12
  22. package/build/popover/Popover.mjs.map +1 -1
  23. package/build/styles/field/Field.css +3 -19
  24. package/build/styles/main.css +3 -21
  25. package/build/types/field/Field.d.ts.map +1 -1
  26. package/build/types/inputs/TextArea.d.ts.map +1 -1
  27. package/build/types/inputs/contexts.d.ts +0 -6
  28. package/build/types/inputs/contexts.d.ts.map +1 -1
  29. package/build/types/popover/Popover.d.ts.map +1 -1
  30. package/build/types/test-utils/index.d.ts +0 -2
  31. package/build/types/test-utils/index.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/field/Field.css +3 -19
  34. package/src/field/Field.less +3 -17
  35. package/src/field/Field.story.tsx +1 -5
  36. package/src/field/Field.test.tsx +0 -90
  37. package/src/field/Field.tsx +37 -84
  38. package/src/i18n/en.json +0 -1
  39. package/src/inputs/TextArea.tsx +3 -34
  40. package/src/inputs/contexts.tsx +1 -18
  41. package/src/main.css +3 -21
  42. package/src/popover/Popover.story.tsx +45 -0
  43. package/src/popover/Popover.test.story.tsx +63 -0
  44. package/src/popover/Popover.test.tsx +56 -0
  45. package/src/popover/Popover.tsx +10 -12
  46. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.story.tsx +0 -1
  47. package/build/field/Field.messages.js +0 -14
  48. package/build/field/Field.messages.js.map +0 -1
  49. package/build/field/Field.messages.mjs +0 -10
  50. package/build/field/Field.messages.mjs.map +0 -1
  51. package/build/types/field/Field.messages.d.ts +0 -8
  52. package/build/types/field/Field.messages.d.ts.map +0 -1
  53. package/src/field/Field.messages.ts +0 -8
  54. package/src/inputs/TextArea.story.tsx +0 -56
@@ -1,14 +1,9 @@
1
1
  import { clsx } from 'clsx';
2
- import { useCallback, useId, useRef, useState } from 'react';
3
- import { useIntl } from 'react-intl';
2
+ import { useId, useRef } from 'react';
4
3
 
5
- import Body from '../body';
6
4
  import { Sentiment } from '../common';
7
- import messages from './Field.messages';
8
5
  import { InlinePrompt, type InlinePromptProps } from '../prompt';
9
6
  import {
10
- TextareaCharacterCountProvider,
11
- type TextareaCharacterCountState,
12
7
  FieldLabelContextProvider,
13
8
  InputDescribedByProvider,
14
9
  InputIdContextProvider,
@@ -59,7 +54,6 @@ export const Field = ({
59
54
  children,
60
55
  ...props
61
56
  }: FieldProps) => {
62
- const { formatMessage } = useIntl();
63
57
  const labelRef = useRef<HTMLLabelElement>(null);
64
58
  const sentiment = props.error ? Sentiment.NEGATIVE : propType;
65
59
  const message = propMessage || props.error;
@@ -72,18 +66,6 @@ export const Field = ({
72
66
 
73
67
  const messageId = useId();
74
68
  const descriptionId = useId();
75
- const textareaCharCounterId = useId();
76
-
77
- const [textareaCharacterCount, setTextareaCharacterCount] =
78
- useState<TextareaCharacterCountState>(null);
79
- const handleTextareaCharacterCount = useCallback(
80
- (state: TextareaCharacterCountState) => setTextareaCharacterCount(state),
81
- [],
82
- );
83
-
84
- const isNearCharLimit =
85
- textareaCharacterCount != null &&
86
- textareaCharacterCount.current >= textareaCharacterCount.max * 0.8;
87
69
 
88
70
  /**
89
71
  * form control can have multiple messages to describe it,
@@ -97,9 +79,6 @@ export const Field = ({
97
79
  if (message) {
98
80
  messageIds.push(messageId);
99
81
  }
100
- if (textareaCharacterCount) {
101
- messageIds.push(textareaCharCounterId);
102
- }
103
82
  return messageIds.length > 0 ? messageIds.join(' ') : undefined;
104
83
  }
105
84
 
@@ -108,69 +87,43 @@ export const Field = ({
108
87
  <InputIdContextProvider value={inputId}>
109
88
  <InputDescribedByProvider value={ariaDescribedbyByIds()}>
110
89
  <InputInvalidProvider value={hasError}>
111
- <TextareaCharacterCountProvider value={handleTextareaCharacterCount}>
112
- <div
113
- className={clsx(
114
- 'np-field form-group d-block',
115
- {
116
- 'has-success': sentiment === Sentiment.POSITIVE,
117
- 'has-warning': sentiment === Sentiment.WARNING,
118
- 'has-error': hasError,
119
- 'has-info': sentiment === Sentiment.NEUTRAL,
120
- },
121
- className,
122
- )}
123
- >
124
- {label != null ? (
125
- <>
126
- <Label ref={labelRef} id={labelId} htmlFor={inputId}>
127
- {required ? label : <Label.Optional>{label}</Label.Optional>}
128
- </Label>
129
- <Label.Description id={descriptionId}>{description}</Label.Description>
130
- <div className="np-field-control">{children}</div>
131
- </>
132
- ) : (
133
- children
134
- )}
90
+ <div
91
+ className={clsx(
92
+ 'np-field form-group d-block',
93
+ {
94
+ 'has-success': sentiment === Sentiment.POSITIVE,
95
+ 'has-warning': sentiment === Sentiment.WARNING,
96
+ 'has-error': hasError,
97
+ 'has-info': sentiment === Sentiment.NEUTRAL,
98
+ },
99
+ className,
100
+ )}
101
+ >
102
+ {label != null ? (
103
+ <>
104
+ <Label ref={labelRef} id={labelId} htmlFor={inputId}>
105
+ {required ? label : <Label.Optional>{label}</Label.Optional>}
106
+ </Label>
107
+ <Label.Description id={descriptionId}>{description}</Label.Description>
108
+ <div className="np-field-control">{children}</div>
109
+ </>
110
+ ) : (
111
+ children
112
+ )}
135
113
 
136
- {(message || textareaCharacterCount) && (
137
- <div className="np-field-validation">
138
- {message && (
139
- <InlinePrompt
140
- sentiment={sentiment}
141
- id={messageId}
142
- mediaLabel={messageIconLabel}
143
- className="np-field__prompt"
144
- loading={messageLoading}
145
- width="full"
146
- >
147
- {message}
148
- </InlinePrompt>
149
- )}
150
- {textareaCharacterCount && (
151
- <Body
152
- as="span"
153
- id={textareaCharCounterId}
154
- {...(isNearCharLimit
155
- ? {
156
- role: 'status' as const,
157
- 'aria-live': 'polite' as const,
158
- 'aria-atomic': 'true' as const,
159
- }
160
- : {})}
161
- aria-label={formatMessage(messages.characterCount, {
162
- current: textareaCharacterCount.current,
163
- max: textareaCharacterCount.max,
164
- })}
165
- className="np-field-textarea-char-counter"
166
- >
167
- {textareaCharacterCount.current}/{textareaCharacterCount.max}
168
- </Body>
169
- )}
170
- </div>
171
- )}
172
- </div>
173
- </TextareaCharacterCountProvider>
114
+ {message && (
115
+ <InlinePrompt
116
+ sentiment={sentiment}
117
+ id={messageId}
118
+ mediaLabel={messageIconLabel}
119
+ className="np-field__prompt"
120
+ loading={messageLoading}
121
+ width="full"
122
+ >
123
+ {message}
124
+ </InlinePrompt>
125
+ )}
126
+ </div>
174
127
  </InputInvalidProvider>
175
128
  </InputDescribedByProvider>
176
129
  </InputIdContextProvider>
package/src/i18n/en.json CHANGED
@@ -20,7 +20,6 @@
20
20
  "neptune.Expander.expandAriaLabel": "Expand",
21
21
  "neptune.ExpressiveMoneyInput.currency.search.placeholder": "Type a currency / country",
22
22
  "neptune.ExpressiveMoneyInput.currency.select.currency": "Select currency",
23
- "neptune.Field.characterCount": "{current} of {max} characters used",
24
23
  "neptune.FlowNavigation.back": "back to previous step",
25
24
  "neptune.Info.ariaLabel": "More information",
26
25
  "neptune.Label.optional": "(Optional)",
@@ -1,9 +1,9 @@
1
1
  import { clsx } from 'clsx';
2
- import { forwardRef, useCallback } from 'react';
2
+ import { forwardRef } from 'react';
3
3
 
4
4
  import { Merge } from '../utils';
5
5
  import { inputClassNameBase } from './_common';
6
- import { useTextareaCharacterCount, useInputAttributes } from './contexts';
6
+ import { useInputAttributes } from './contexts';
7
7
 
8
8
  export interface TextAreaProps extends Merge<
9
9
  React.ComponentPropsWithRef<'textarea'>,
@@ -13,48 +13,17 @@ export interface TextAreaProps extends Merge<
13
13
  > {}
14
14
 
15
15
  export const TextArea = forwardRef(function TextArea(
16
- { className, maxLength, onChange, ...restProps }: TextAreaProps,
16
+ { className, ...restProps }: TextAreaProps,
17
17
  reference: React.ForwardedRef<HTMLTextAreaElement | null>,
18
18
  ) {
19
19
  const inputAttributes = useInputAttributes();
20
- const value = restProps.value ?? restProps.defaultValue ?? '';
21
- const currentLength = getGraphemeLength(typeof value === 'string' ? value : String(value));
22
-
23
- useTextareaCharacterCount(currentLength, maxLength);
24
-
25
- const handleChange = useCallback(
26
- (e: React.ChangeEvent<HTMLTextAreaElement>) => {
27
- if (maxLength != null && getGraphemeLength(e.target.value) > maxLength) {
28
- return;
29
- }
30
- onChange?.(e);
31
- },
32
- [maxLength, onChange],
33
- );
34
20
 
35
21
  return (
36
22
  <textarea
37
23
  ref={reference}
38
24
  className={clsx(className, inputClassNameBase(), 'np-text-area')}
39
- onChange={handleChange}
40
25
  {...inputAttributes}
41
26
  {...restProps}
42
27
  />
43
28
  );
44
29
  });
45
-
46
- const segmenter =
47
- typeof Intl.Segmenter === 'function'
48
- ? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
49
- : undefined;
50
-
51
- function getGraphemeLength(str: string): number {
52
- if (segmenter) {
53
- let count = 0;
54
- for (const _ of segmenter.segment(str)) {
55
- count += 1;
56
- }
57
- return count;
58
- }
59
- return Array.from(str).length;
60
- }
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, useEffect } from 'react';
1
+ import { createContext, useContext } from 'react';
2
2
 
3
3
  type FieldLabelContextType = {
4
4
  id?: string;
@@ -36,23 +36,6 @@ export function useFieldLabelRef() {
36
36
  return useContext(FieldLabelContext)?.ref;
37
37
  }
38
38
 
39
- export type TextareaCharacterCountState = { current: number; max: number } | null;
40
-
41
- const TextareaCharacterCountContext = createContext<
42
- ((state: TextareaCharacterCountState) => void) | undefined
43
- >(undefined);
44
- export const TextareaCharacterCountProvider = TextareaCharacterCountContext.Provider;
45
-
46
- export function useTextareaCharacterCount(current: number, max: number | undefined) {
47
- const setCharacterCount = useContext(TextareaCharacterCountContext);
48
- useEffect(() => {
49
- if (setCharacterCount && max != null) {
50
- setCharacterCount({ current, max });
51
- return () => setCharacterCount(null);
52
- }
53
- }, [setCharacterCount, current, max]);
54
- }
55
-
56
39
  export interface WithInputAttributesProps {
57
40
  inputAttributes: ReturnType<typeof useInputAttributes>;
58
41
  }
package/src/main.css CHANGED
@@ -30194,36 +30194,18 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
30194
30194
  stroke-dasharray: var(--wds-list-item-spotlight-strokeDashSize) var(--wds-list-item-spotlight-strokeDashSize);
30195
30195
  }
30196
30196
 
30197
- .np-field-control {
30197
+ .np-field-control,
30198
+ .np-field__prompt {
30198
30199
  margin-top: 4px;
30199
30200
  margin-top: var(--size-4);
30200
30201
  }
30201
30202
 
30202
- .np-field-validation {
30203
- display: flex;
30204
- align-items: flex-start;
30205
- margin-top: 4px;
30206
- margin-top: var(--size-4);
30207
- gap: 8px;
30208
- gap: var(--size-8);
30209
- }
30210
-
30211
- .np-field-textarea-char-counter {
30212
- min-width: 55px;
30213
- text-align: right;
30214
- margin-left: auto;
30215
- padding: 4px 0;
30216
- padding: var(--size-4) 0;
30217
- color: #768e9c;
30218
- color: var(--color-content-tertiary);
30219
- }
30220
-
30221
30203
  .np-field .form-group--typeahead[class],
30222
30204
  .np-field .np-checkbox-label[class] {
30223
30205
  margin-bottom: 0;
30224
30206
  }
30225
30207
 
30226
- .np-field:has(.wds-radio-group) .np-field-validation {
30208
+ .np-field:has(.wds-radio-group) .np-field__prompt {
30227
30209
  margin-top: 12px;
30228
30210
  margin-top: var(--size-12);
30229
30211
  }
@@ -71,3 +71,48 @@ export const WithoutVisibleTitle: Story = {
71
71
  ),
72
72
  },
73
73
  } satisfies Meta<typeof Popover>;
74
+
75
+ /**
76
+ * The `preferredPlacement` prop indicates where the Popover should appear
77
+ * relative to its trigger element. However, it is a preference, not a guarantee.
78
+ *
79
+ * If there isn't enough viewport space in the preferred direction, the Popover
80
+ * automatically flips to an alternative placement. The fallback order is:
81
+ * - `top` → tries `bottom`, then `right`, then `left`
82
+ * - `bottom` → tries `top`, then `right`, then `left`
83
+ * - `left` → tries `right`, then `top`, then `bottom`
84
+ * - `right` → tries `left`, then `top`, then `bottom`
85
+ *
86
+ * For example, if you set `preferredPlacement="top"` but the trigger is near
87
+ * the top of the viewport, the Popover will flip to `bottom` instead.
88
+ *
89
+ * Try scrolling this story so triggers are near viewport edges to see the
90
+ * flip behaviour in action.
91
+ */
92
+ export const PreferredPlacement: Story = {
93
+ render: () => (
94
+ <div
95
+ style={{
96
+ display: 'flex',
97
+ flexWrap: 'wrap',
98
+ gap: '48px',
99
+ justifyContent: 'center',
100
+ alignItems: 'center',
101
+ padding: '120px 80px',
102
+ }}
103
+ >
104
+ {([Position.TOP, Position.RIGHT, Position.BOTTOM, Position.LEFT] as const).map(
105
+ (placement) => (
106
+ <Popover
107
+ key={placement}
108
+ preferredPlacement={placement}
109
+ title="Guaranteed rate"
110
+ content={<Content />}
111
+ >
112
+ <Button v2>{placement}</Button>
113
+ </Popover>
114
+ ),
115
+ )}
116
+ </div>
117
+ ),
118
+ };
@@ -0,0 +1,63 @@
1
+ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
+ import { userEvent, within } from 'storybook/test';
3
+
4
+ import Button from '../button';
5
+ import { Position } from '../common';
6
+
7
+ import Popover, { PopoverPreferredPlacement } from './Popover';
8
+
9
+ export default {
10
+ component: Popover,
11
+ title: 'Dialogs/Popover/Tests',
12
+ tags: ['!autodocs', '!manifest'],
13
+ } satisfies Meta<typeof Popover>;
14
+
15
+ type Story = StoryObj<typeof Popover>;
16
+
17
+ const createPlacementStory = (
18
+ placement: PopoverPreferredPlacement,
19
+ containerStyle?: React.CSSProperties,
20
+ ): Story => ({
21
+ parameters: {
22
+ chromatic: {
23
+ delay: 1000,
24
+ },
25
+ },
26
+ render: () => (
27
+ <div style={containerStyle ?? { display: 'flex', justifyContent: 'center', padding: '150px' }}>
28
+ <Popover preferredPlacement={placement} title="Info" content="Details">
29
+ <Button v2>{placement}</Button>
30
+ </Popover>
31
+ </div>
32
+ ),
33
+ play: async ({ canvasElement }) => {
34
+ const canvas = within(canvasElement);
35
+ await userEvent.click(canvas.getByRole('button'));
36
+ },
37
+ });
38
+
39
+ export const PlacementTop: Story = createPlacementStory(Position.TOP);
40
+ export const PlacementRight: Story = createPlacementStory(Position.RIGHT);
41
+ export const PlacementBottom: Story = createPlacementStory(Position.BOTTOM);
42
+ export const PlacementLeft: Story = createPlacementStory(Position.LEFT);
43
+
44
+ export const FlipTop: Story = createPlacementStory(Position.TOP, {
45
+ position: 'absolute',
46
+ top: 0,
47
+ left: '50%',
48
+ });
49
+ export const FlipRight: Story = createPlacementStory(Position.RIGHT, {
50
+ position: 'absolute',
51
+ top: '50%',
52
+ right: 0,
53
+ });
54
+ export const FlipBottom: Story = createPlacementStory(Position.BOTTOM, {
55
+ position: 'absolute',
56
+ bottom: 0,
57
+ left: '50%',
58
+ });
59
+ export const FlipLeft: Story = createPlacementStory(Position.LEFT, {
60
+ position: 'absolute',
61
+ top: '50%',
62
+ left: 0,
63
+ });
@@ -142,6 +142,62 @@ describe('Popover', () => {
142
142
  expect(handleOnClose).toHaveBeenCalledTimes(1);
143
143
  });
144
144
 
145
+ describe('className', () => {
146
+ describe('on desktop', () => {
147
+ beforeEach(() => {
148
+ global.innerWidth = Breakpoint.SMALL;
149
+ });
150
+
151
+ it('applies className to the panel container', async () => {
152
+ render(
153
+ <Popover {...props} className="custom-popover-class">
154
+ <button type="button">Open</button>
155
+ </Popover>,
156
+ );
157
+
158
+ await userEvent.click(screen.getByText('Open'));
159
+ await waitForPanel();
160
+
161
+ const panel = getPanel();
162
+ expect(panel).toHaveClass('np-popover__container');
163
+ expect(panel).toHaveClass('custom-popover-class');
164
+ });
165
+ });
166
+
167
+ describe('on mobile', () => {
168
+ beforeEach(() => {
169
+ global.innerWidth = Breakpoint.SMALL - 1;
170
+ });
171
+
172
+ it('applies className to the bottom sheet container', async () => {
173
+ render(
174
+ <Popover {...props} className="custom-popover-class">
175
+ <button type="button">Open</button>
176
+ </Popover>,
177
+ );
178
+
179
+ await userEvent.click(screen.getByText('Open'));
180
+
181
+ const bottomSheet = getBottomSheet();
182
+ expect(bottomSheet).toHaveClass('np-popover__container');
183
+ expect(bottomSheet).toHaveClass('custom-popover-class');
184
+ });
185
+ });
186
+
187
+ it('does not apply className to the trigger wrapper', () => {
188
+ const { container } = render(
189
+ <Popover {...props} className="custom-popover-class">
190
+ <button type="button">Open</button>
191
+ </Popover>,
192
+ );
193
+
194
+ const wrapper = container.firstChild;
195
+ expect(wrapper).toHaveClass('np-popover');
196
+ expect(wrapper).toHaveClass('d-inline-block');
197
+ expect(wrapper).not.toHaveClass('custom-popover-class');
198
+ });
199
+ });
200
+
145
201
  const waitForPanel = async () => screen.findByText('content');
146
202
  const getDimmer = () => document.querySelector('.dimmer');
147
203
  const getPanel = () => document.querySelector('.np-panel');
@@ -75,17 +75,15 @@ export default function Popover({
75
75
  };
76
76
 
77
77
  return (
78
- <span className={clsx('np-popover', className)}>
79
- <span ref={anchorReference} className="d-inline-block">
80
- {isValidElement<{ onClick?: () => void }>(children)
81
- ? cloneElement(children, {
82
- onClick: () => {
83
- children.props.onClick?.();
84
- setOpen((prevOpen) => !prevOpen);
85
- },
86
- })
87
- : children}
88
- </span>
78
+ <span ref={anchorReference} className="np-popover d-inline-block">
79
+ {isValidElement<{ onClick?: () => void }>(children)
80
+ ? cloneElement(children, {
81
+ onClick: () => {
82
+ children.props.onClick?.();
83
+ setOpen((prevOpen) => !prevOpen);
84
+ },
85
+ })
86
+ : children}
89
87
  <ResponsivePanel
90
88
  aria-label={ariaLabel}
91
89
  aria-labelledby={title && !ariaLabel ? titleId : undefined}
@@ -93,7 +91,7 @@ export default function Popover({
93
91
  anchorRef={anchorReference}
94
92
  position={resolvedPlacement}
95
93
  arrow
96
- className="np-popover__container"
94
+ className={clsx('np-popover__container', className)}
97
95
  onClose={handleOnClose}
98
96
  >
99
97
  <div className="np-popover__content np-text-default-body">
@@ -21,7 +21,6 @@ export const Basic: Story = {
21
21
  <TextareaWithDisplayFormat
22
22
  id={id}
23
23
  value="0000"
24
- maxLength={20}
25
24
  displayPattern="**** - **** - ****"
26
25
  onChange={console.log}
27
26
  />
@@ -1,14 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var reactIntl = require('react-intl');
6
-
7
- var messages = reactIntl.defineMessages({
8
- characterCount: {
9
- id: "neptune.Field.characterCount"
10
- }
11
- });
12
-
13
- exports.default = messages;
14
- //# sourceMappingURL=Field.messages.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Field.messages.js","sources":["../../src/field/Field.messages.ts"],"sourcesContent":["import { defineMessages } from 'react-intl';\n\nexport default defineMessages({\n characterCount: {\n id: 'neptune.Field.characterCount',\n defaultMessage: '{current} of {max} characters used',\n },\n});\n"],"names":["defineMessages","characterCount","id"],"mappings":";;;;;;AAEA,eAAeA,wBAAc,CAAC;AAC5BC,EAAAA,cAAc,EAAE;IACdC,EAAE,EAAA;AAEH;AACF,CAAA,CAAC;;;;"}
@@ -1,10 +0,0 @@
1
- import { defineMessages } from 'react-intl';
2
-
3
- var messages = defineMessages({
4
- characterCount: {
5
- id: "neptune.Field.characterCount"
6
- }
7
- });
8
-
9
- export { messages as default };
10
- //# sourceMappingURL=Field.messages.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Field.messages.mjs","sources":["../../src/field/Field.messages.ts"],"sourcesContent":["import { defineMessages } from 'react-intl';\n\nexport default defineMessages({\n characterCount: {\n id: 'neptune.Field.characterCount',\n defaultMessage: '{current} of {max} characters used',\n },\n});\n"],"names":["defineMessages","characterCount","id"],"mappings":";;AAEA,eAAeA,cAAc,CAAC;AAC5BC,EAAAA,cAAc,EAAE;IACdC,EAAE,EAAA;AAEH;AACF,CAAA,CAAC;;;;"}
@@ -1,8 +0,0 @@
1
- declare const _default: {
2
- characterCount: {
3
- id: string;
4
- defaultMessage: string;
5
- };
6
- };
7
- export default _default;
8
- //# sourceMappingURL=Field.messages.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Field.messages.d.ts","sourceRoot":"","sources":["../../../src/field/Field.messages.ts"],"names":[],"mappings":";;;;;;AAEA,wBAKG"}
@@ -1,8 +0,0 @@
1
- import { defineMessages } from 'react-intl';
2
-
3
- export default defineMessages({
4
- characterCount: {
5
- id: 'neptune.Field.characterCount',
6
- defaultMessage: '{current} of {max} characters used',
7
- },
8
- });
@@ -1,56 +0,0 @@
1
- import { useState } from 'react';
2
- import { Meta, StoryObj } from '@storybook/react-webpack5';
3
-
4
- import { Field } from '../field/Field';
5
- import { Sentiment } from '../common';
6
- import { TextArea } from './TextArea';
7
-
8
- const meta: Meta<typeof TextArea> = {
9
- title: 'Forms/TextArea',
10
- component: TextArea,
11
- };
12
-
13
- export default meta;
14
- type Story = StoryObj<typeof TextArea>;
15
-
16
- export const Basic: Story = {
17
- render: () => {
18
- const [value, setValue] = useState('');
19
-
20
- return (
21
- <Field label="Message" required={false}>
22
- <TextArea maxLength={10} value={value} onChange={({ target }) => setValue(target.value)} />
23
- </Field>
24
- );
25
- },
26
- parameters: {
27
- docs: {
28
- source: {
29
- code: `<Field label="Message" required={false}>
30
- <TextArea
31
- maxLength={200}
32
- value={value}
33
- onChange={({ target }) => setValue(target.value)}
34
- />
35
- </Field>`,
36
- },
37
- },
38
- },
39
- };
40
-
41
- export const WithError: Story = {
42
- render: () => {
43
- const [value, setValue] = useState('');
44
-
45
- return (
46
- <Field
47
- label="Message"
48
- required={false}
49
- sentiment={Sentiment.NEGATIVE}
50
- message="You have exceeded the character limit"
51
- >
52
- <TextArea maxLength={200} value={value} onChange={({ target }) => setValue(target.value)} />
53
- </Field>
54
- );
55
- },
56
- };