@equinor/eds-core-react 2.3.6 → 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 +407 -64
- package/build/index.min.css +1 -1
- package/dist/eds-core-react.cjs +199 -28
- 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/DatePicker.js +4 -1
- package/dist/esm/components/Datepicker/DateRangePicker.js +4 -1
- 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/Datepicker/utils/getLocalizedValidationErrors.js +164 -0
- package/dist/esm/components/InputWrapper/InputWrapper.js +1 -1
- package/dist/esm/components/Popover/Popover.js +4 -4
- package/dist/esm/components/Table/DataCell/DataCell.js +1 -1
- package/dist/esm/components/Table/FooterCell/FooterCell.js +1 -1
- package/dist/esm/components/Table/HeaderCell/HeaderCell.js +1 -1
- 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/TextField/TextField.js +7 -2
- 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 +451 -8
- package/dist/types/components/Autocomplete/AutocompleteContext.d.ts +2 -2
- package/dist/types/components/Autocomplete/useAutocomplete.d.ts +2 -2
- package/dist/types/components/Datepicker/utils/getLocalizedValidationErrors.d.ts +9 -0
- 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 +42 -35
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { DateFormatter } from '@internationalized/date';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validation message translations matching @react-stately/datepicker's built-in messages.
|
|
5
|
+
* Sourced from @react-stately/datepicker's intlStrings bundle.
|
|
6
|
+
* Unsupported locales fall back to English.
|
|
7
|
+
*/
|
|
8
|
+
const allTranslations = {
|
|
9
|
+
'en-US': {
|
|
10
|
+
rangeUnderflow: args => `Value must be ${args.minValue} or later.`,
|
|
11
|
+
rangeOverflow: args => `Value must be ${args.maxValue} or earlier.`,
|
|
12
|
+
unavailableDate: 'Selected date unavailable.'
|
|
13
|
+
},
|
|
14
|
+
'nb-NO': {
|
|
15
|
+
rangeUnderflow: args => `Verdien m\u00e5 v\u00e6re ${args.minValue} eller senere.`,
|
|
16
|
+
rangeOverflow: args => `Verdien m\u00e5 v\u00e6re ${args.maxValue} eller tidligere.`,
|
|
17
|
+
unavailableDate: 'Valgt dato utilgjengelig.'
|
|
18
|
+
},
|
|
19
|
+
'da-DK': {
|
|
20
|
+
rangeUnderflow: args => `V\u00e6rdien skal v\u00e6re ${args.minValue} eller senere.`,
|
|
21
|
+
rangeOverflow: args => `V\u00e6rdien skal v\u00e6re ${args.maxValue} eller tidligere.`,
|
|
22
|
+
unavailableDate: 'Den valgte dato er ikke tilg\u00e6ngelig.'
|
|
23
|
+
},
|
|
24
|
+
'sv-SE': {
|
|
25
|
+
rangeUnderflow: args => `V\u00e4rdet m\u00e5ste vara ${args.minValue} eller senare.`,
|
|
26
|
+
rangeOverflow: args => `V\u00e4rdet m\u00e5ste vara ${args.maxValue} eller tidigare.`,
|
|
27
|
+
unavailableDate: 'Valt datum \u00e4r inte tillg\u00e4ngligt.'
|
|
28
|
+
},
|
|
29
|
+
'de-DE': {
|
|
30
|
+
rangeUnderflow: args => `Der Wert muss ${args.minValue} oder sp\u00e4ter sein.`,
|
|
31
|
+
rangeOverflow: args => `Der Wert muss ${args.maxValue} oder fr\u00fcher sein.`,
|
|
32
|
+
unavailableDate: 'Das ausgew\u00e4hlte Datum ist nicht verf\u00fcgbar.'
|
|
33
|
+
},
|
|
34
|
+
'fr-FR': {
|
|
35
|
+
rangeUnderflow: args => `La valeur doit \u00eatre ${args.minValue} ou ult\u00e9rieure.`,
|
|
36
|
+
rangeOverflow: args => `La valeur doit \u00eatre ${args.maxValue} ou ant\u00e9rieure.`,
|
|
37
|
+
unavailableDate: 'Date s\u00e9lectionn\u00e9e non disponible.'
|
|
38
|
+
},
|
|
39
|
+
'es-ES': {
|
|
40
|
+
rangeUnderflow: args => `El valor debe ser ${args.minValue} o posterior.`,
|
|
41
|
+
rangeOverflow: args => `El valor debe ser ${args.maxValue} o anterior.`,
|
|
42
|
+
unavailableDate: 'Fecha seleccionada no disponible.'
|
|
43
|
+
},
|
|
44
|
+
'pt-BR': {
|
|
45
|
+
rangeUnderflow: args => `O valor deve ser ${args.minValue} ou posterior.`,
|
|
46
|
+
rangeOverflow: args => `O valor deve ser ${args.maxValue} ou anterior.`,
|
|
47
|
+
unavailableDate: 'Data selecionada indispon\u00edvel.'
|
|
48
|
+
},
|
|
49
|
+
'pt-PT': {
|
|
50
|
+
rangeUnderflow: args => `O valor tem de ser ${args.minValue} ou posterior.`,
|
|
51
|
+
rangeOverflow: args => `O valor tem de ser ${args.maxValue} ou anterior.`,
|
|
52
|
+
unavailableDate: 'Data selecionada indispon\u00edvel.'
|
|
53
|
+
},
|
|
54
|
+
'pl-PL': {
|
|
55
|
+
rangeUnderflow: args => `Warto\u015b\u0107 musi wynosi\u0107 ${args.minValue} lub p\u00f3\u017aniej.`,
|
|
56
|
+
rangeOverflow: args => `Warto\u015b\u0107 musi wynosi\u0107 ${args.maxValue} lub wcze\u015bniej.`,
|
|
57
|
+
unavailableDate: 'Wybrana data jest niedost\u0119pna.'
|
|
58
|
+
},
|
|
59
|
+
'nl-NL': {
|
|
60
|
+
rangeUnderflow: args => `Waarde moet ${args.minValue} of later zijn.`,
|
|
61
|
+
rangeOverflow: args => `Waarde moet ${args.maxValue} of eerder zijn.`,
|
|
62
|
+
unavailableDate: 'Geselecteerde datum niet beschikbaar.'
|
|
63
|
+
},
|
|
64
|
+
'it-IT': {
|
|
65
|
+
rangeUnderflow: args => `Il valore deve essere ${args.minValue} o successivo.`,
|
|
66
|
+
rangeOverflow: args => `Il valore deve essere ${args.maxValue} o precedente.`,
|
|
67
|
+
unavailableDate: 'Data selezionata non disponibile.'
|
|
68
|
+
},
|
|
69
|
+
'ja-JP': {
|
|
70
|
+
rangeUnderflow: args => `\u5024\u306f${args.minValue}\u4ee5\u964d\u3067\u3042\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002`,
|
|
71
|
+
rangeOverflow: args => `\u5024\u306f${args.maxValue}\u4ee5\u524d\u3067\u3042\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002`,
|
|
72
|
+
unavailableDate: '\u9078\u629e\u3057\u305f\u65e5\u4ed8\u306f\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002'
|
|
73
|
+
},
|
|
74
|
+
'ko-KR': {
|
|
75
|
+
rangeUnderflow: args => `\uac12\uc740 ${args.minValue} \uc774\ud6c4\uc5ec\uc57c \ud569\ub2c8\ub2e4.`,
|
|
76
|
+
rangeOverflow: args => `\uac12\uc740 ${args.maxValue} \uc774\uc804\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4.`,
|
|
77
|
+
unavailableDate: '\uc120\ud0dd\ud55c \ub0a0\uc9dc\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.'
|
|
78
|
+
},
|
|
79
|
+
'zh-CN': {
|
|
80
|
+
rangeUnderflow: args => `\u503c\u5fc5\u987b\u4e3a ${args.minValue} \u6216\u66f4\u665a\u3002`,
|
|
81
|
+
rangeOverflow: args => `\u503c\u5fc5\u987b\u4e3a ${args.maxValue} \u6216\u66f4\u65e9\u3002`,
|
|
82
|
+
unavailableDate: '\u6240\u9009\u65e5\u671f\u4e0d\u53ef\u7528\u3002'
|
|
83
|
+
},
|
|
84
|
+
'zh-TW': {
|
|
85
|
+
rangeUnderflow: args => `\u503c\u5fc5\u9808\u70ba ${args.minValue} \u6216\u66f4\u665a\u3002`,
|
|
86
|
+
rangeOverflow: args => `\u503c\u5fc5\u9808\u70ba ${args.maxValue} \u6216\u66f4\u65e9\u3002`,
|
|
87
|
+
unavailableDate: '\u6240\u9078\u65e5\u671f\u4e0d\u53ef\u7528\u3002'
|
|
88
|
+
},
|
|
89
|
+
'ru-RU': {
|
|
90
|
+
rangeUnderflow: args => `\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c ${args.minValue} \u0438\u043b\u0438 \u043f\u043e\u0437\u0436\u0435.`,
|
|
91
|
+
rangeOverflow: args => `\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c ${args.maxValue} \u0438\u043b\u0438 \u0440\u0430\u043d\u044c\u0448\u0435.`,
|
|
92
|
+
unavailableDate: '\u0412\u044b\u0431\u0440\u0430\u043d\u043d\u0430\u044f \u0434\u0430\u0442\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430.'
|
|
93
|
+
},
|
|
94
|
+
'uk-UA': {
|
|
95
|
+
rangeUnderflow: args => `\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 ${args.minValue} \u0430\u0431\u043e \u043f\u0456\u0437\u043d\u0456\u0448\u0435.`,
|
|
96
|
+
rangeOverflow: args => `\u0417\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 ${args.maxValue} \u0430\u0431\u043e \u0440\u0430\u043d\u0456\u0448\u0435.`,
|
|
97
|
+
unavailableDate: '\u0412\u0438\u0431\u0440\u0430\u043d\u0430 \u0434\u0430\u0442\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430.'
|
|
98
|
+
},
|
|
99
|
+
'ar-AE': {
|
|
100
|
+
rangeUnderflow: args => `\u064a\u062c\u0628 \u0623\u0646 \u062a\u0643\u0648\u0646 \u0627\u0644\u0642\u064a\u0645\u0629 ${args.minValue} \u0623\u0648 \u0623\u062d\u062f\u062b.`,
|
|
101
|
+
rangeOverflow: args => `\u064a\u062c\u0628 \u0623\u0646 \u062a\u0643\u0648\u0646 \u0627\u0644\u0642\u064a\u0645\u0629 ${args.maxValue} \u0623\u0648 \u0623\u0642\u062f\u0645.`,
|
|
102
|
+
unavailableDate: '\u0627\u0644\u062a\u0627\u0631\u064a\u062e \u0627\u0644\u0645\u062d\u062f\u062f \u063a\u064a\u0631 \u0645\u062a\u0627\u062d.'
|
|
103
|
+
},
|
|
104
|
+
'fi-FI': {
|
|
105
|
+
rangeUnderflow: args => `Arvon on oltava ${args.minValue} tai my\u00f6hempi.`,
|
|
106
|
+
rangeOverflow: args => `Arvon on oltava ${args.maxValue} tai aikaisempi.`,
|
|
107
|
+
unavailableDate: 'Valittu p\u00e4iv\u00e4m\u00e4\u00e4r\u00e4 ei ole k\u00e4ytett\u00e4viss\u00e4.'
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const englishFallback = allTranslations['en-US'];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Find the best matching locale from available translations.
|
|
114
|
+
* Tries exact match first (e.g. "nb-NO"), then language prefix match (e.g. "nb" → "nb-NO").
|
|
115
|
+
*/
|
|
116
|
+
function findLocaleMessages(locale) {
|
|
117
|
+
if (allTranslations[locale]) return allTranslations[locale];
|
|
118
|
+
const language = Intl.Locale ? new Intl.Locale(locale).language : locale.split('-')[0];
|
|
119
|
+
|
|
120
|
+
// First match wins: e.g. "pt" → "pt-BR", "zh" → "zh-CN"
|
|
121
|
+
for (const key of Object.keys(allTranslations)) {
|
|
122
|
+
const keyLang = key.split('-')[0];
|
|
123
|
+
if (keyLang === language) return allTranslations[key];
|
|
124
|
+
}
|
|
125
|
+
return englishFallback;
|
|
126
|
+
}
|
|
127
|
+
function formatMessage(msg, args) {
|
|
128
|
+
return typeof msg === 'function' ? msg(args) : msg;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generates validation error messages using the provided locale instead of
|
|
133
|
+
* navigator.language (which is what react-stately uses internally).
|
|
134
|
+
*
|
|
135
|
+
* This fixes the issue where validation messages appear in the browser's
|
|
136
|
+
* language rather than the locale configured via I18nProvider.
|
|
137
|
+
*/
|
|
138
|
+
function getLocalizedValidationErrors(validationDetails, locale, minValue, maxValue, timezone) {
|
|
139
|
+
const msgs = findLocaleMessages(locale);
|
|
140
|
+
const dateFormatter = new DateFormatter(locale, {
|
|
141
|
+
year: 'numeric',
|
|
142
|
+
month: 'numeric',
|
|
143
|
+
day: 'numeric'
|
|
144
|
+
});
|
|
145
|
+
const timeZone = timezone ?? dateFormatter.resolvedOptions().timeZone;
|
|
146
|
+
const errors = [];
|
|
147
|
+
if (validationDetails.rangeUnderflow && minValue && msgs.rangeUnderflow) {
|
|
148
|
+
errors.push(formatMessage(msgs.rangeUnderflow, {
|
|
149
|
+
minValue: dateFormatter.format(minValue.toDate(timeZone))
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
if (validationDetails.rangeOverflow && maxValue && msgs.rangeOverflow) {
|
|
153
|
+
errors.push(formatMessage(msgs.rangeOverflow, {
|
|
154
|
+
maxValue: dateFormatter.format(maxValue.toDate(timeZone))
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
// react-stately maps isDateUnavailable to badInput (not customError)
|
|
158
|
+
if (validationDetails.badInput && msgs.unavailableDate) {
|
|
159
|
+
errors.push(formatMessage(msgs.unavailableDate, {}));
|
|
160
|
+
}
|
|
161
|
+
return errors;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export { getLocalizedValidationErrors };
|
|
@@ -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(() => {
|
|
@@ -20,7 +20,7 @@ const StyledTableCell = styled.td.withConfig({
|
|
|
20
20
|
align
|
|
21
21
|
} = theme;
|
|
22
22
|
const backgroundColor = color === 'error' ? theme.validation.error?.background : '';
|
|
23
|
-
const base = css(["min-height:", ";height:", ";background:", ";vertical-align:", ";box-sizing:border-box;", " ", " ", ""], height, height, backgroundColor, align.vertical, spacingsTemplate(spacings), typographyTemplate(typography), bordersTemplate(border));
|
|
23
|
+
const base = css(["min-height:", ";height:", ";background:", ";vertical-align:", ";box-sizing:border-box;", " ", " ", " a{font-size:inherit;font-weight:inherit;}"], height, height, backgroundColor, align.vertical, spacingsTemplate(spacings), typographyTemplate(typography), bordersTemplate(border));
|
|
24
24
|
return base;
|
|
25
25
|
});
|
|
26
26
|
const TableDataCell = /*#__PURE__*/forwardRef(function TableDataCell({
|
|
@@ -19,7 +19,7 @@ const StyledTableCell = styled.th.withConfig({
|
|
|
19
19
|
typography,
|
|
20
20
|
spacings
|
|
21
21
|
} = theme;
|
|
22
|
-
return css(["min-height:", ";height:", ";background:", ";box-sizing:border-box;", " ", " ", " ", ""], height, height, background, spacingsTemplate(spacings), typographyTemplate(typography), bordersTemplate(theme.border), $sticky ? css(["position:sticky;bottom:0;z-index:2;"]) : '');
|
|
22
|
+
return css(["min-height:", ";height:", ";background:", ";box-sizing:border-box;", " ", " ", " a{font-size:inherit;font-weight:inherit;}", ""], height, height, background, spacingsTemplate(spacings), typographyTemplate(typography), bordersTemplate(theme.border), $sticky ? css(["position:sticky;bottom:0;z-index:2;"]) : '');
|
|
23
23
|
});
|
|
24
24
|
const CellInner = styled.div.withConfig({
|
|
25
25
|
displayName: "FooterCell__CellInner",
|
|
@@ -34,7 +34,7 @@ const StyledTableCell = styled.th.withConfig({
|
|
|
34
34
|
// Firefox specific workaround (bug in v142.0) - see issue #3910
|
|
35
35
|
// Hardcoded padding values compensate for Firefox's incorrect table cell height calculation
|
|
36
36
|
const firefoxFix = isFirefox() ? css(["vertical-align:top;height:auto;min-height:", ";> div{padding:", " 0;}"], height, $density === 'compact' ? '7px' : '13px') : css([""]);
|
|
37
|
-
return css(["min-height:", ";height:", ";background:", ";box-sizing:border-box;", " ", " ", " ", " ", " ", " ", ""], height, height, background, spacingsTemplate(spacings), typographyTemplate(typography), bordersTemplate(theme.border), sortStylingHover, sortStylingActive, firefoxFix, $sticky ? css(["position:sticky;top:0;z-index:1;"]) : '');
|
|
37
|
+
return css(["min-height:", ";height:", ";background:", ";box-sizing:border-box;", " ", " ", " ", " ", " ", " a{font-size:inherit;font-weight:inherit;}", ""], height, height, background, spacingsTemplate(spacings), typographyTemplate(typography), bordersTemplate(theme.border), sortStylingHover, sortStylingActive, firefoxFix, $sticky ? css(["position:sticky;top:0;z-index:1;"]) : '');
|
|
38
38
|
});
|
|
39
39
|
const CellInner = styled.div.withConfig({
|
|
40
40
|
displayName: "HeaderCell__CellInner",
|
|
@@ -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 };
|