@equinor/eds-core-react 2.3.7 → 2.4.0-beta.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.
- package/build/index.css +389 -39
- package/build/index.min.css +1 -1
- package/dist/eds-core-react.cjs +29 -23
- package/dist/esm/components/Autocomplete/AutocompleteContext.js +2 -2
- package/dist/esm/components/Autocomplete/MultipleInput.js +2 -2
- package/dist/esm/components/Autocomplete/OptionList.js +6 -4
- package/dist/esm/components/Autocomplete/useAutocomplete.js +4 -0
- package/dist/esm/components/Datepicker/calendars/CalendarGrid.js +4 -8
- package/dist/esm/components/Datepicker/calendars/CalendarHeader.js +6 -6
- package/dist/esm/components/Datepicker/fields/DateSegment.js +4 -1
- package/dist/esm/components/InputWrapper/InputWrapper.js +1 -1
- package/dist/esm/components/Popover/Popover.js +4 -4
- package/dist/esm/components/Textarea/Textarea.js +1 -1
- package/dist/esm-next/components/next/Banner/Banner.js +88 -0
- package/dist/esm-next/components/next/Button/Button.js +1 -0
- package/dist/esm-next/components/next/Icon/Icon.js +27 -1
- package/dist/esm-next/components/next/Input/Input.js +1 -1
- package/dist/esm-next/components/next/Link/Link.js +24 -0
- package/dist/esm-next/components/next/Search/Search.js +118 -0
- package/dist/esm-next/components/next/TextArea/TextArea.js +131 -0
- package/dist/esm-next/components/next/Tooltip/Tooltip.js +84 -0
- package/dist/esm-next/index.next.js +5 -0
- package/dist/index.next.cjs +445 -6
- package/dist/types/components/Autocomplete/AutocompleteContext.d.ts +2 -2
- package/dist/types/components/Autocomplete/useAutocomplete.d.ts +2 -2
- package/dist/types/components/next/Banner/Banner.d.ts +23 -0
- package/dist/types/components/next/Banner/Banner.figma.d.ts +1 -0
- package/dist/types/components/next/Banner/Banner.types.d.ts +33 -0
- package/dist/types/components/next/Banner/index.d.ts +2 -0
- package/dist/types/components/next/Icon/Icon.d.ts +0 -1
- package/dist/types/components/next/Input/Input.types.d.ts +4 -1
- package/dist/types/components/next/Link/Link.d.ts +4 -0
- package/dist/types/components/next/Link/Link.figma.d.ts +1 -0
- package/dist/types/components/next/Link/Link.types.d.ts +11 -0
- package/dist/types/components/next/Link/index.d.ts +2 -0
- package/dist/types/components/next/Search/Search.d.ts +9 -0
- package/dist/types/components/next/Search/Search.figma.d.ts +1 -0
- package/dist/types/components/next/Search/Search.types.d.ts +16 -0
- package/dist/types/components/next/Search/index.d.ts +2 -0
- package/dist/types/components/next/TextArea/TextArea.d.ts +11 -0
- package/dist/types/components/next/TextArea/TextArea.figma.d.ts +1 -0
- package/dist/types/components/next/TextArea/TextArea.types.d.ts +21 -0
- package/dist/types/components/next/TextArea/index.d.ts +2 -0
- package/dist/types/components/next/Tooltip/Tooltip.d.ts +7 -0
- package/dist/types/components/next/Tooltip/Tooltip.figma.d.ts +1 -0
- package/dist/types/components/next/Tooltip/Tooltip.types.d.ts +17 -0
- package/dist/types/components/next/Tooltip/index.d.ts +2 -0
- package/dist/types/components/next/index.d.ts +10 -0
- package/package.json +41 -34
|
@@ -3,9 +3,9 @@ import styled from 'styled-components';
|
|
|
3
3
|
import { useAutocompleteContext } from './AutocompleteContext.js';
|
|
4
4
|
import { RightAdornments } from './RightAdornments.js';
|
|
5
5
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
-
import { Chip } from '../Chip/Chip.js';
|
|
7
6
|
import { useEds } from '../EdsProvider/eds.context.js';
|
|
8
7
|
import { Input } from '../Input/Input.js';
|
|
8
|
+
import { Chip } from '../Chip/Chip.js';
|
|
9
9
|
|
|
10
10
|
const UnstyledInput = styled.input.withConfig({
|
|
11
11
|
displayName: "MultipleInput__UnstyledInput",
|
|
@@ -76,7 +76,7 @@ const MultipleInput = () => {
|
|
|
76
76
|
if (el) chipRefs.current.set(getLabel(item), el);else chipRefs.current.delete(getLabel(item));
|
|
77
77
|
},
|
|
78
78
|
style: {
|
|
79
|
-
outline: '1px solid var(--
|
|
79
|
+
outline: '1px solid var(--eds_interactive_primary__resting, rgba(0, 112, 121, 1))',
|
|
80
80
|
...(density === 'compact' && {
|
|
81
81
|
height: '16px',
|
|
82
82
|
fontSize: '12px',
|
|
@@ -41,10 +41,12 @@ const OptionList = ({
|
|
|
41
41
|
|
|
42
42
|
// MARK: popover toggle
|
|
43
43
|
useIsomorphicLayoutEffect(() => {
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
if (refs.floating.current?.hasAttribute('popover')) {
|
|
45
|
+
if (isOpen) {
|
|
46
|
+
refs.floating.current.showPopover();
|
|
47
|
+
} else {
|
|
48
|
+
refs.floating.current.hidePopover();
|
|
49
|
+
}
|
|
48
50
|
}
|
|
49
51
|
}, [isOpen, refs.floating]);
|
|
50
52
|
const showNoOptions = isOpen && !availableItems.length && noOptionsText.length > 0;
|
|
@@ -43,6 +43,7 @@ const useAutocomplete = ({
|
|
|
43
43
|
variant,
|
|
44
44
|
onClear,
|
|
45
45
|
ref,
|
|
46
|
+
id,
|
|
46
47
|
...other
|
|
47
48
|
}) => {
|
|
48
49
|
const [lastScrollOffset, setLastScrollOffset] = useState(0);
|
|
@@ -204,6 +205,9 @@ const useAutocomplete = ({
|
|
|
204
205
|
|
|
205
206
|
// MARK: downshift state
|
|
206
207
|
let comboBoxProps = {
|
|
208
|
+
...(id !== undefined && {
|
|
209
|
+
inputId: id
|
|
210
|
+
}),
|
|
207
211
|
items: availableItems,
|
|
208
212
|
//can not pass readonly type to downshift so we cast it to regular T[]
|
|
209
213
|
initialSelectedItem: initialSelectedOptions[0],
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getWeeksInMonth } from '@internationalized/date';
|
|
1
|
+
import { useCalendarGrid } from 'react-aria';
|
|
3
2
|
import { CalendarCell } from './CalendarCell.js';
|
|
4
3
|
import { YearGrid } from './YearGrid.js';
|
|
5
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
@@ -17,9 +16,6 @@ function CalendarGrid({
|
|
|
17
16
|
setYearPickerPage,
|
|
18
17
|
...props
|
|
19
18
|
}) {
|
|
20
|
-
const {
|
|
21
|
-
locale
|
|
22
|
-
} = useLocale();
|
|
23
19
|
const {
|
|
24
20
|
gridProps,
|
|
25
21
|
headerProps,
|
|
@@ -29,9 +25,9 @@ function CalendarGrid({
|
|
|
29
25
|
weekdayStyle: 'long'
|
|
30
26
|
}, state);
|
|
31
27
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
const weeksInMonthArray = [...new Array(
|
|
28
|
+
// Always render 6 rows (the maximum weeks in any month) so the calendar
|
|
29
|
+
// height stays consistent when navigating between months.
|
|
30
|
+
const weeksInMonthArray = [...new Array(6).keys()];
|
|
35
31
|
return showYearPicker ? /*#__PURE__*/jsx(YearGrid, {
|
|
36
32
|
year: state.focusedDate.year,
|
|
37
33
|
setFocusedYear: year => {
|
|
@@ -21,7 +21,7 @@ function TodayPicker({
|
|
|
21
21
|
onClick: () => onClick(new CalendarDate(today.getFullYear(), today.getMonth() + 1, today.getDate())),
|
|
22
22
|
variant: 'ghost',
|
|
23
23
|
style: {
|
|
24
|
-
marginLeft:
|
|
24
|
+
marginLeft: 4
|
|
25
25
|
},
|
|
26
26
|
children: "Today"
|
|
27
27
|
});
|
|
@@ -30,6 +30,10 @@ const HeaderActions = styled.div.withConfig({
|
|
|
30
30
|
displayName: "CalendarHeader__HeaderActions",
|
|
31
31
|
componentId: "sc-kuy15-1"
|
|
32
32
|
})(["display:flex;align-items:center;width:100%;"]);
|
|
33
|
+
const TitleButton = styled(Button).withConfig({
|
|
34
|
+
displayName: "CalendarHeader__TitleButton",
|
|
35
|
+
componentId: "sc-kuy15-2"
|
|
36
|
+
})(["min-width:13.1rem;white-space:nowrap;font-size:", ";text-transform:capitalize;& > span{display:flex;justify-content:space-between;align-items:center;width:100%;}"], tokens.typography.heading.h5.fontSize);
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
39
|
* The default header for the calendar components if no custom header is provided
|
|
@@ -61,15 +65,11 @@ function CalendarHeader({
|
|
|
61
65
|
style: {
|
|
62
66
|
flex: '1 1 auto'
|
|
63
67
|
}
|
|
64
|
-
}), /*#__PURE__*/jsxs(
|
|
68
|
+
}), /*#__PURE__*/jsxs(TitleButton, {
|
|
65
69
|
onClick: () => setShowYearPicker(!showYearPicker),
|
|
66
70
|
"data-testid": 'heading',
|
|
67
71
|
"aria-live": 'polite',
|
|
68
72
|
variant: 'ghost',
|
|
69
|
-
style: {
|
|
70
|
-
fontSize: tokens.typography.heading.h5.fontSize,
|
|
71
|
-
textTransform: 'capitalize'
|
|
72
|
-
},
|
|
73
73
|
children: [title, /*#__PURE__*/jsx(Icon, {
|
|
74
74
|
data: showYearPicker ? chevron_up : chevron_down
|
|
75
75
|
})]
|
|
@@ -30,7 +30,10 @@ function DateSegment({
|
|
|
30
30
|
formatOptions,
|
|
31
31
|
timezone
|
|
32
32
|
} = useDatePickerContext();
|
|
33
|
-
const formatter = useDateFormatter(
|
|
33
|
+
const formatter = useDateFormatter({
|
|
34
|
+
...formatOptions,
|
|
35
|
+
timeZone: timezone
|
|
36
|
+
});
|
|
34
37
|
const parts = state.value ? formatter.formatToParts(state.value.toDate(timezone)) : [];
|
|
35
38
|
const part = parts.find(p => p.type === segment.type);
|
|
36
39
|
const value = segment.isPlaceholder || segment.type === 'literal' ? segment.text : part?.value ?? segment.text;
|
|
@@ -3,9 +3,9 @@ import styled, { ThemeProvider } from 'styled-components';
|
|
|
3
3
|
import { useToken } from '@equinor/eds-utils';
|
|
4
4
|
import { inputToken } from './InputWrapper.tokens.js';
|
|
5
5
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
import { useEds } from '../EdsProvider/eds.context.js';
|
|
6
7
|
import { Label as Label$1 } from '../Label/Label.js';
|
|
7
8
|
import { HelperText as TextfieldHelperText } from './HelperText/HelperText.js';
|
|
8
|
-
import { useEds } from '../EdsProvider/eds.context.js';
|
|
9
9
|
|
|
10
10
|
const Container = styled.div.withConfig({
|
|
11
11
|
displayName: "InputWrapper__Container",
|
|
@@ -99,12 +99,12 @@ const Popover = /*#__PURE__*/forwardRef(function Popover({
|
|
|
99
99
|
} = useInteractions([useDismiss(context)]);
|
|
100
100
|
useEffect(() => {
|
|
101
101
|
if (!elements.floating) return;
|
|
102
|
-
if (
|
|
103
|
-
if (
|
|
102
|
+
if (elements.floating.hasAttribute('popover')) {
|
|
103
|
+
if (open) {
|
|
104
104
|
elements.floating.showPopover();
|
|
105
|
+
} else {
|
|
106
|
+
elements.floating.hidePopover();
|
|
105
107
|
}
|
|
106
|
-
} else {
|
|
107
|
-
elements.floating.hidePopover();
|
|
108
108
|
}
|
|
109
109
|
}, [open, elements.floating]);
|
|
110
110
|
useEffect(() => {
|
|
@@ -4,8 +4,8 @@ import { input as input$1 } from '../Input/Input.tokens.js';
|
|
|
4
4
|
import { useAutoResize, mergeRefs } from '@equinor/eds-utils';
|
|
5
5
|
import { jsx } from 'react/jsx-runtime';
|
|
6
6
|
import { useInputField } from '../InputWrapper/useInputField.js';
|
|
7
|
-
import { InputWrapper } from '../InputWrapper/InputWrapper.js';
|
|
8
7
|
import { useEds } from '../EdsProvider/eds.context.js';
|
|
8
|
+
import { InputWrapper } from '../InputWrapper/InputWrapper.js';
|
|
9
9
|
import { Input } from '../Input/Input.js';
|
|
10
10
|
|
|
11
11
|
const {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { close } from '@equinor/eds-icons';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { Button } from '../Button/Button.js';
|
|
5
|
+
import { Icon } from '../Icon/Icon.js';
|
|
6
|
+
import { TypographyNext } from '../../Typography/Typography.new.js';
|
|
7
|
+
|
|
8
|
+
const BannerIcon = /*#__PURE__*/forwardRef(function BannerIcon({
|
|
9
|
+
className,
|
|
10
|
+
children,
|
|
11
|
+
...rest
|
|
12
|
+
}, ref) {
|
|
13
|
+
return /*#__PURE__*/jsx("span", {
|
|
14
|
+
ref: ref,
|
|
15
|
+
className: ['eds-banner__icon', className].filter(Boolean).join(' '),
|
|
16
|
+
...rest,
|
|
17
|
+
children: children
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
const BannerMessage = /*#__PURE__*/forwardRef(function BannerMessage({
|
|
21
|
+
className,
|
|
22
|
+
children,
|
|
23
|
+
...rest
|
|
24
|
+
}, ref) {
|
|
25
|
+
return /*#__PURE__*/jsx(TypographyNext, {
|
|
26
|
+
ref: ref,
|
|
27
|
+
as: "p",
|
|
28
|
+
family: "ui",
|
|
29
|
+
size: "md",
|
|
30
|
+
baseline: "center",
|
|
31
|
+
lineHeight: "default",
|
|
32
|
+
tracking: "normal",
|
|
33
|
+
className: ['eds-banner__message', className].filter(Boolean).join(' '),
|
|
34
|
+
...rest,
|
|
35
|
+
children: children
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
const BannerActions = /*#__PURE__*/forwardRef(function BannerActions({
|
|
39
|
+
placement = 'left',
|
|
40
|
+
className,
|
|
41
|
+
children,
|
|
42
|
+
...rest
|
|
43
|
+
}, ref) {
|
|
44
|
+
return /*#__PURE__*/jsx("div", {
|
|
45
|
+
ref: ref,
|
|
46
|
+
className: ['eds-banner__actions', className].filter(Boolean).join(' '),
|
|
47
|
+
"data-placement": placement,
|
|
48
|
+
...rest,
|
|
49
|
+
children: children
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
const BannerComponent = /*#__PURE__*/forwardRef(function Banner({
|
|
53
|
+
tone = 'info',
|
|
54
|
+
role = 'status',
|
|
55
|
+
onDismiss,
|
|
56
|
+
className,
|
|
57
|
+
children,
|
|
58
|
+
...rest
|
|
59
|
+
}, ref) {
|
|
60
|
+
return /*#__PURE__*/jsxs("div", {
|
|
61
|
+
ref: ref,
|
|
62
|
+
className: ['eds-banner', className].filter(Boolean).join(' '),
|
|
63
|
+
"data-color-appearance": tone,
|
|
64
|
+
role: role,
|
|
65
|
+
...rest,
|
|
66
|
+
children: [children, onDismiss && /*#__PURE__*/jsx(Button, {
|
|
67
|
+
variant: "ghost",
|
|
68
|
+
icon: true,
|
|
69
|
+
size: "small",
|
|
70
|
+
className: "eds-banner__dismiss",
|
|
71
|
+
"aria-label": "Dismiss",
|
|
72
|
+
onClick: onDismiss,
|
|
73
|
+
children: /*#__PURE__*/jsx(Icon, {
|
|
74
|
+
data: close
|
|
75
|
+
})
|
|
76
|
+
})]
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
BannerIcon.displayName = 'Banner.Icon';
|
|
80
|
+
BannerMessage.displayName = 'Banner.Message';
|
|
81
|
+
BannerActions.displayName = 'Banner.Actions';
|
|
82
|
+
BannerComponent.displayName = 'Banner';
|
|
83
|
+
const Banner = BannerComponent;
|
|
84
|
+
Banner.Icon = BannerIcon;
|
|
85
|
+
Banner.Message = BannerMessage;
|
|
86
|
+
Banner.Actions = BannerActions;
|
|
87
|
+
|
|
88
|
+
export { Banner };
|
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import { forwardRef, useId } from 'react';
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Icon component for EDS 2.0
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Automatic sizing from parent's data-font-size via --eds-typography-icon-size
|
|
9
|
+
* - Dynamic fallback sizing (1.5em) when no tokens are set
|
|
10
|
+
* - Explicit size prop for standalone usage
|
|
11
|
+
* - WCAG 2.1 AA accessible with optional title for semantic icons
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { Icon } from '@equinor/eds-core-react/next'
|
|
16
|
+
* import { save } from '@equinor/eds-icons'
|
|
17
|
+
*
|
|
18
|
+
* // Auto-sized from parent's data-font-size
|
|
19
|
+
* <div data-font-size="md">
|
|
20
|
+
* <Icon data={warning} /> Error message
|
|
21
|
+
* </div>
|
|
22
|
+
*
|
|
23
|
+
* // Explicit size for standalone usage
|
|
24
|
+
* <Icon data={save} size="lg" />
|
|
25
|
+
*
|
|
26
|
+
* // Semantic icon with accessible name
|
|
27
|
+
* <Icon data={save} title="Save document" />
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
4
30
|
const Icon = /*#__PURE__*/forwardRef(function Icon({
|
|
5
31
|
data,
|
|
6
32
|
title,
|
|
@@ -19,7 +45,7 @@ const Icon = /*#__PURE__*/forwardRef(function Icon({
|
|
|
19
45
|
height = '24',
|
|
20
46
|
width = '24'
|
|
21
47
|
} = data;
|
|
22
|
-
const classes = ['icon', className].filter(Boolean).join(' ');
|
|
48
|
+
const classes = ['eds-icon', className].filter(Boolean).join(' ');
|
|
23
49
|
|
|
24
50
|
// Accessibility: decorative icons are hidden, semantic icons have role="img"
|
|
25
51
|
const accessibilityProps = title ? {
|
|
@@ -56,7 +56,7 @@ const Input = /*#__PURE__*/forwardRef(function Input({
|
|
|
56
56
|
})]
|
|
57
57
|
}), /*#__PURE__*/jsx(Component, {
|
|
58
58
|
ref: ref,
|
|
59
|
-
type: type,
|
|
59
|
+
type: Component === 'textarea' ? undefined : type,
|
|
60
60
|
disabled: disabled,
|
|
61
61
|
readOnly: readOnly,
|
|
62
62
|
className: ['eds-input', className].filter(Boolean).join(' '),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
const Link = /*#__PURE__*/forwardRef(function Link({
|
|
5
|
+
variant = 'inline',
|
|
6
|
+
className,
|
|
7
|
+
children,
|
|
8
|
+
...rest
|
|
9
|
+
}, ref) {
|
|
10
|
+
const classes = ['eds-link', className].filter(Boolean).join(' ');
|
|
11
|
+
return /*#__PURE__*/jsx("a", {
|
|
12
|
+
ref: ref,
|
|
13
|
+
className: classes,
|
|
14
|
+
"data-variant": variant,
|
|
15
|
+
"data-font-family": variant === 'standalone' ? 'ui' : undefined,
|
|
16
|
+
"data-font-size": variant === 'standalone' ? 'md' : undefined,
|
|
17
|
+
"data-line-height": variant === 'standalone' ? 'squished' : undefined,
|
|
18
|
+
...rest,
|
|
19
|
+
children: children
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
Link.displayName = 'Link';
|
|
23
|
+
|
|
24
|
+
export { Link };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { forwardRef, useState, useRef, useCallback } from 'react';
|
|
2
|
+
import { close, search } from '@equinor/eds-icons';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
import { useFieldIds } from '../Field/useFieldIds.js';
|
|
5
|
+
import { Field } from '../Field/Field.js';
|
|
6
|
+
import { Input } from '../Input/Input.js';
|
|
7
|
+
import { Button } from '../Button/Button.js';
|
|
8
|
+
import { Icon } from '../Icon/Icon.js';
|
|
9
|
+
|
|
10
|
+
const Search = /*#__PURE__*/forwardRef(function Search({
|
|
11
|
+
label,
|
|
12
|
+
description,
|
|
13
|
+
helperMessage,
|
|
14
|
+
id: providedId,
|
|
15
|
+
invalid,
|
|
16
|
+
disabled,
|
|
17
|
+
readOnly,
|
|
18
|
+
value,
|
|
19
|
+
defaultValue,
|
|
20
|
+
onChange,
|
|
21
|
+
onClear,
|
|
22
|
+
clearLabel = 'Clear search',
|
|
23
|
+
...inputProps
|
|
24
|
+
}, forwardedRef) {
|
|
25
|
+
const {
|
|
26
|
+
inputId,
|
|
27
|
+
labelId,
|
|
28
|
+
descriptionId,
|
|
29
|
+
helperMessageId,
|
|
30
|
+
getDescribedBy
|
|
31
|
+
} = useFieldIds(providedId);
|
|
32
|
+
const isControlled = value !== undefined;
|
|
33
|
+
const [internalHasValue, setInternalHasValue] = useState(() => Boolean(defaultValue));
|
|
34
|
+
const hasValue = isControlled ? Boolean(value) : internalHasValue;
|
|
35
|
+
const inputRef = useRef(null);
|
|
36
|
+
const mergedRef = useCallback(node => {
|
|
37
|
+
inputRef.current = node;
|
|
38
|
+
if (typeof forwardedRef === 'function') {
|
|
39
|
+
forwardedRef(node);
|
|
40
|
+
} else if (forwardedRef) {
|
|
41
|
+
forwardedRef.current = node;
|
|
42
|
+
}
|
|
43
|
+
}, [forwardedRef]);
|
|
44
|
+
const handleChange = e => {
|
|
45
|
+
if (!isControlled) {
|
|
46
|
+
setInternalHasValue(Boolean(e.target.value));
|
|
47
|
+
}
|
|
48
|
+
onChange?.(e);
|
|
49
|
+
};
|
|
50
|
+
const handleClear = () => {
|
|
51
|
+
if (!isControlled && inputRef.current) {
|
|
52
|
+
// Direct DOM mutation: bypasses React's synthetic onChange, which is
|
|
53
|
+
// intentional — onClear is the designated callback for clear actions.
|
|
54
|
+
inputRef.current.value = '';
|
|
55
|
+
setInternalHasValue(false);
|
|
56
|
+
}
|
|
57
|
+
onClear?.();
|
|
58
|
+
inputRef.current?.focus();
|
|
59
|
+
};
|
|
60
|
+
const showClear = hasValue && !disabled && !readOnly;
|
|
61
|
+
// Accent only in interactive states — grey in error, readonly, disabled
|
|
62
|
+
const iconTone = disabled || readOnly || invalid ? 'neutral' : 'accent';
|
|
63
|
+
return /*#__PURE__*/jsx("search", {
|
|
64
|
+
className: "eds-search",
|
|
65
|
+
"aria-labelledby": label ? labelId : undefined,
|
|
66
|
+
children: /*#__PURE__*/jsxs(Field, {
|
|
67
|
+
disabled: disabled,
|
|
68
|
+
children: [label && /*#__PURE__*/jsx(Field.Label, {
|
|
69
|
+
id: labelId,
|
|
70
|
+
htmlFor: inputId,
|
|
71
|
+
children: label
|
|
72
|
+
}), description && /*#__PURE__*/jsx(Field.Description, {
|
|
73
|
+
id: descriptionId,
|
|
74
|
+
children: description
|
|
75
|
+
}), /*#__PURE__*/jsx(Input, {
|
|
76
|
+
ref: mergedRef,
|
|
77
|
+
id: inputId,
|
|
78
|
+
type: "search",
|
|
79
|
+
disabled: disabled,
|
|
80
|
+
readOnly: readOnly,
|
|
81
|
+
invalid: invalid,
|
|
82
|
+
value: value,
|
|
83
|
+
defaultValue: defaultValue,
|
|
84
|
+
onChange: handleChange,
|
|
85
|
+
"aria-describedby": getDescribedBy({
|
|
86
|
+
hasDescription: !!description,
|
|
87
|
+
hasHelperMessage: !!helperMessage
|
|
88
|
+
}),
|
|
89
|
+
hideErrorIcon: true,
|
|
90
|
+
startAdornment: /*#__PURE__*/jsx(Icon, {
|
|
91
|
+
data: search,
|
|
92
|
+
className: "search-icon",
|
|
93
|
+
"data-color-appearance": iconTone
|
|
94
|
+
}),
|
|
95
|
+
endAdornment: showClear ? /*#__PURE__*/jsx(Button, {
|
|
96
|
+
variant: "ghost",
|
|
97
|
+
icon: true,
|
|
98
|
+
round: true,
|
|
99
|
+
size: "small",
|
|
100
|
+
tone: invalid ? 'neutral' : 'accent',
|
|
101
|
+
onClick: handleClear,
|
|
102
|
+
"aria-label": clearLabel,
|
|
103
|
+
children: /*#__PURE__*/jsx(Icon, {
|
|
104
|
+
data: close
|
|
105
|
+
})
|
|
106
|
+
}) : undefined,
|
|
107
|
+
...inputProps
|
|
108
|
+
}), helperMessage && /*#__PURE__*/jsx(Field.HelperMessage, {
|
|
109
|
+
id: helperMessageId,
|
|
110
|
+
role: invalid ? 'alert' : undefined,
|
|
111
|
+
children: helperMessage
|
|
112
|
+
})]
|
|
113
|
+
})
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
Search.displayName = 'Search';
|
|
117
|
+
|
|
118
|
+
export { Search };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { forwardRef, useState, useEffect, useRef, useMemo } from 'react';
|
|
2
|
+
import { useAutoResize, mergeRefs } from '@equinor/eds-utils';
|
|
3
|
+
import { info_circle } from '@equinor/eds-icons';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
import { useFieldIds } from '../Field/useFieldIds.js';
|
|
6
|
+
import { Field } from '../Field/Field.js';
|
|
7
|
+
import { Tooltip } from '../../Tooltip/Tooltip.js';
|
|
8
|
+
import { Button } from '../Button/Button.js';
|
|
9
|
+
import { Icon } from '../Icon/Icon.js';
|
|
10
|
+
import { Input } from '../Input/Input.js';
|
|
11
|
+
|
|
12
|
+
const TextArea = /*#__PURE__*/forwardRef(function TextArea({
|
|
13
|
+
label,
|
|
14
|
+
labelInfo,
|
|
15
|
+
indicator,
|
|
16
|
+
description,
|
|
17
|
+
helperMessage,
|
|
18
|
+
id: providedId,
|
|
19
|
+
invalid,
|
|
20
|
+
disabled,
|
|
21
|
+
maxRows,
|
|
22
|
+
showCharacterCount,
|
|
23
|
+
...textareaProps
|
|
24
|
+
}, ref) {
|
|
25
|
+
const {
|
|
26
|
+
inputId,
|
|
27
|
+
descriptionId,
|
|
28
|
+
helperMessageId,
|
|
29
|
+
getDescribedBy
|
|
30
|
+
} = useFieldIds(providedId);
|
|
31
|
+
const [charCount, setCharCount] = useState(() => String(textareaProps.value ?? textareaProps.defaultValue ?? '').length);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (textareaProps.value !== undefined) {
|
|
34
|
+
setCharCount(String(textareaProps.value).length);
|
|
35
|
+
}
|
|
36
|
+
}, [textareaProps.value]);
|
|
37
|
+
const {
|
|
38
|
+
maxLength,
|
|
39
|
+
onChange: onChangeProp,
|
|
40
|
+
...restTextareaProps
|
|
41
|
+
} = textareaProps;
|
|
42
|
+
const handleChange = e => {
|
|
43
|
+
setCharCount(e.target.value.length);
|
|
44
|
+
onChangeProp?.(e);
|
|
45
|
+
};
|
|
46
|
+
const internalRef = useRef(null);
|
|
47
|
+
const [maxPixelHeight, setMaxPixelHeight] = useState(undefined);
|
|
48
|
+
|
|
49
|
+
// Auto-grow is always on. When maxRows is set, compute a pixel cap after
|
|
50
|
+
// mount using the element's actual rendered line-height and padding
|
|
51
|
+
// (density-aware). Until the cap is computed (or if maxRows is not set),
|
|
52
|
+
// pass Infinity so the textarea grows without bound.
|
|
53
|
+
const autoResizeHeight = maxRows !== undefined && maxPixelHeight !== undefined ? maxPixelHeight : Infinity;
|
|
54
|
+
const autoResizeRef = useAutoResize(autoResizeHeight);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!maxRows || !internalRef.current) return;
|
|
57
|
+
const el = internalRef.current;
|
|
58
|
+
const updateMaxHeight = () => {
|
|
59
|
+
const style = window.getComputedStyle(el);
|
|
60
|
+
const lineHeight = parseFloat(style.lineHeight);
|
|
61
|
+
const paddingBlockStart = parseFloat(style.paddingBlockStart);
|
|
62
|
+
const paddingBlockEnd = parseFloat(style.paddingBlockEnd);
|
|
63
|
+
setMaxPixelHeight(lineHeight * maxRows + paddingBlockStart + paddingBlockEnd);
|
|
64
|
+
};
|
|
65
|
+
const observer = new ResizeObserver(updateMaxHeight);
|
|
66
|
+
observer.observe(el);
|
|
67
|
+
updateMaxHeight();
|
|
68
|
+
return () => observer.disconnect();
|
|
69
|
+
}, [maxRows]);
|
|
70
|
+
const combinedRef = useMemo(() => mergeRefs(ref, autoResizeRef, internalRef), [ref, autoResizeRef, internalRef]);
|
|
71
|
+
const showHelperRow = helperMessage || showCharacterCount;
|
|
72
|
+
return /*#__PURE__*/jsxs(Field, {
|
|
73
|
+
className: "eds-text-area",
|
|
74
|
+
disabled: disabled,
|
|
75
|
+
children: [label && /*#__PURE__*/jsxs("div", {
|
|
76
|
+
className: "label-row",
|
|
77
|
+
children: [/*#__PURE__*/jsx(Field.Label, {
|
|
78
|
+
htmlFor: inputId,
|
|
79
|
+
indicator: indicator,
|
|
80
|
+
children: label
|
|
81
|
+
}), labelInfo && /*#__PURE__*/jsx(Tooltip, {
|
|
82
|
+
title: labelInfo,
|
|
83
|
+
placement: "top",
|
|
84
|
+
children: /*#__PURE__*/jsx(Button, {
|
|
85
|
+
variant: "ghost",
|
|
86
|
+
icon: true,
|
|
87
|
+
round: true,
|
|
88
|
+
size: "small",
|
|
89
|
+
tone: "neutral",
|
|
90
|
+
"aria-label": "More information",
|
|
91
|
+
children: /*#__PURE__*/jsx(Icon, {
|
|
92
|
+
data: info_circle,
|
|
93
|
+
size: "xs"
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
})]
|
|
97
|
+
}), description && /*#__PURE__*/jsx(Field.Description, {
|
|
98
|
+
id: descriptionId,
|
|
99
|
+
children: description
|
|
100
|
+
}), /*#__PURE__*/jsx(Input, {
|
|
101
|
+
ref: combinedRef,
|
|
102
|
+
as: "textarea",
|
|
103
|
+
id: inputId,
|
|
104
|
+
disabled: disabled,
|
|
105
|
+
invalid: invalid,
|
|
106
|
+
maxLength: maxLength,
|
|
107
|
+
onChange: handleChange,
|
|
108
|
+
"aria-describedby": getDescribedBy({
|
|
109
|
+
hasDescription: !!description,
|
|
110
|
+
hasHelperMessage: !!helperMessage
|
|
111
|
+
}),
|
|
112
|
+
...restTextareaProps
|
|
113
|
+
}), showHelperRow && /*#__PURE__*/jsxs("div", {
|
|
114
|
+
className: "helper-row",
|
|
115
|
+
children: [helperMessage && /*#__PURE__*/jsx(Field.HelperMessage, {
|
|
116
|
+
id: helperMessageId,
|
|
117
|
+
role: invalid ? 'alert' : undefined,
|
|
118
|
+
children: helperMessage
|
|
119
|
+
}), showCharacterCount && /*#__PURE__*/jsx("span", {
|
|
120
|
+
className: "char-count",
|
|
121
|
+
"data-font-family": "ui",
|
|
122
|
+
"data-font-size": "xs",
|
|
123
|
+
"aria-live": maxLength !== undefined && charCount >= maxLength * 0.8 ? 'polite' : 'off',
|
|
124
|
+
children: maxLength !== undefined ? `${charCount} / ${maxLength}` : charCount
|
|
125
|
+
})]
|
|
126
|
+
})]
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
TextArea.displayName = 'TextArea';
|
|
130
|
+
|
|
131
|
+
export { TextArea };
|