@festo-ui/react 9.0.1-dev.760 → 9.0.1-dev.762

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.
@@ -0,0 +1,153 @@
1
+ .fr-combobox-wrapper {
2
+ width: inherit;
3
+ min-width: 64px;
4
+ font-size: var(--fwe-font-size-base);
5
+ flex-direction: column;
6
+ padding-bottom: 18px;
7
+ display: flex;
8
+ position: relative;
9
+ }
10
+
11
+ .fr-combobox-label {
12
+ height: 18px;
13
+ color: var(--fwe-text);
14
+ font-size: var(--fwe-font-size-small);
15
+ line-height: var(--fwe-line-height-base);
16
+ font-weight: var(--fwe-font-weight-bold);
17
+ order: 1;
18
+ }
19
+
20
+ .fr-combobox-control {
21
+ order: 2;
22
+ position: relative;
23
+ }
24
+
25
+ .fr-combobox-input {
26
+ width: 100%;
27
+ height: 33px;
28
+ font-size: var(--fwe-font-size-base);
29
+ appearance: none;
30
+ border: none;
31
+ border-bottom: 1px solid var(--fwe-control-border);
32
+ white-space: nowrap;
33
+ text-overflow: ellipsis;
34
+ cursor: pointer;
35
+ background: none;
36
+ outline: 0;
37
+ padding: 4px 24px 4px 0;
38
+ display: block;
39
+ overflow: hidden;
40
+ }
41
+
42
+ .fr-combobox-input:hover:not(:disabled), .fr-combobox-input:focus:not(:disabled) {
43
+ color: var(--fwe-hero);
44
+ border-color: var(--fwe-hero);
45
+ }
46
+
47
+ .fr-combobox-button {
48
+ cursor: pointer;
49
+ background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyNHYyNEgweiIgZGF0YS1uYW1lPSJ3aGl0ZSBiYWNrZ3JvdW5kIi8+PHBhdGggZmlsbD0iIzMzMyIgZD0iTTUuNjM2IDkuMTcyIDEyIDE1LjUzNmw2LjM2NC02LjM2NC0uNzA3LS43MDhMMTIgMTQuMTIxIDYuMzQzIDguNDY0bC0uNzA3LjcwOHoiIGRhdGEtbmFtZT0iY29udGVudCIvPjwvc3ZnPg==) center / 24px 24px no-repeat;
50
+ border: none;
51
+ width: 24px;
52
+ height: 24px;
53
+ padding: 0;
54
+ position: absolute;
55
+ top: 50%;
56
+ right: 0;
57
+ transform: translateY(-50%);
58
+ }
59
+
60
+ .fr-combobox-button:hover {
61
+ background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyNHYyNEgweiIgZGF0YS1uYW1lPSJ3aGl0ZSBiYWNrZ3JvdW5kIi8+PHBhdGggZmlsbD0iIzAwOTFkYyIgZD0iTTUuNjM2IDkuMTcyIDEyIDE1LjUzNmw2LjM2NC02LjM2NC0uNzA3LS43MDhMMTIgMTQuMTIxIDYuMzQzIDguNDY0bC0uNzA3LjcwOHoiIGRhdGEtbmFtZT0iY29udGVudCIvPjwvc3ZnPg==) center / 24px 24px no-repeat;
62
+ }
63
+
64
+ .fr-combobox-description, .fr-combobox-invalid {
65
+ width: 100%;
66
+ font-size: var(--fwe-font-size-small);
67
+ line-height: var(--fwe-line-height-base);
68
+ position: absolute;
69
+ bottom: 0;
70
+ }
71
+
72
+ .fr-combobox-description {
73
+ color: var(--fwe-text-disabled);
74
+ }
75
+
76
+ .fr-combobox-invalid {
77
+ color: var(--fwe-red);
78
+ display: none;
79
+ }
80
+
81
+ .fr-combobox-wrapper.fr-disabled .fr-combobox-input {
82
+ cursor: default;
83
+ color: var(--fwe-text-disabled);
84
+ border-color: var(--fwe-control-disabled);
85
+ }
86
+
87
+ .fr-combobox-wrapper.fr-disabled .fr-combobox-label {
88
+ color: var(--fwe-text-disabled);
89
+ }
90
+
91
+ .fr-combobox-wrapper.fr-invalid .fr-combobox-input {
92
+ border-color: var(--fwe-red);
93
+ }
94
+
95
+ .fr-combobox-wrapper.fr-invalid .fr-combobox-invalid {
96
+ display: block;
97
+ }
98
+
99
+ .fr-combobox-wrapper.fr-invalid .fr-combobox-description {
100
+ display: none;
101
+ }
102
+
103
+ .fr-combobox-options-container {
104
+ min-width: var(--input-width, 100%);
105
+ background-color: var(--fwe-white);
106
+ border: 1px solid var(--fwe-gray-200);
107
+ border-radius: 4px;
108
+ margin: 0;
109
+ padding: 8px;
110
+ list-style: none;
111
+ box-shadow: 0 1px 4px #00000029;
112
+ }
113
+
114
+ .fr-combobox-option {
115
+ cursor: pointer;
116
+ min-height: 24px;
117
+ padding: 12px 8px;
118
+ line-height: 24px;
119
+ position: relative;
120
+ }
121
+
122
+ .fr-combobox-option:hover, .fr-combobox-option:focus {
123
+ background-color: var(--fwe-gray-100);
124
+ outline: none;
125
+ }
126
+
127
+ .fr-combobox-option:last-child {
128
+ border-bottom: none;
129
+ }
130
+
131
+ .fr-combobox-option.fr-disabled {
132
+ color: var(--fwe-text-disabled);
133
+ cursor: default;
134
+ }
135
+
136
+ .fr-combobox-option-content {
137
+ white-space: nowrap;
138
+ text-overflow: ellipsis;
139
+ min-height: 24px;
140
+ line-height: 24px;
141
+ display: block;
142
+ overflow: hidden;
143
+ }
144
+
145
+ .fr-combobox-highlight {
146
+ color: var(--fwe-hero);
147
+ }
148
+
149
+ .fr-combobox-empty {
150
+ cursor: default;
151
+ color: var(--fwe-text-disabled);
152
+ }
153
+
@@ -0,0 +1,27 @@
1
+ import './ComboBox.scss';
2
+ import { type ComponentPropsWithoutRef, type Ref } from 'react';
3
+ export interface ComboBoxOptionType<T> {
4
+ readonly label: string;
5
+ readonly data: T;
6
+ readonly disabled?: boolean;
7
+ }
8
+ export interface ComboBoxProps<T> extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'defaultValue'> {
9
+ readonly defaultValue?: T;
10
+ readonly value?: T;
11
+ readonly label?: string;
12
+ readonly options?: ComboBoxOptionType<T>[];
13
+ readonly required?: boolean;
14
+ readonly onChange?: (value: T) => void;
15
+ readonly disabled?: boolean;
16
+ readonly name?: string;
17
+ readonly hint?: string;
18
+ readonly error?: string;
19
+ readonly placeholder?: string;
20
+ readonly onInputChange?: (value: string) => void;
21
+ readonly emptyMessage?: string;
22
+ }
23
+ declare function ComboBoxComponent<T>({ defaultValue, value: controlled, label, options, onChange, required, disabled, name, id: idProp, hint, error, placeholder, onInputChange, emptyMessage, className, ...props }: ComboBoxProps<T>, ref: Ref<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
24
+ export declare const ComboBox: <T>(props: ComboBoxProps<T> & {
25
+ ref?: Ref<HTMLDivElement>;
26
+ }) => ReturnType<typeof ComboBoxComponent>;
27
+ export {};
@@ -0,0 +1,165 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import "./ComboBox.css";
3
+ import { Combobox, ComboboxButton, ComboboxInput, ComboboxOption, ComboboxOptions } from "@headlessui/react";
4
+ import classnames from "classnames";
5
+ import { forwardRef, useMemo, useState } from "react";
6
+ import { useControlled } from "../../utils/useControlled.js";
7
+ import { useId } from "../../utils/useId.js";
8
+ function getHighlightedLabel(label, query) {
9
+ if (!query) return [
10
+ {
11
+ text: label,
12
+ highlighted: false
13
+ }
14
+ ];
15
+ const parts = [];
16
+ const lowerLabel = label.toLowerCase();
17
+ const lowerQuery = query.toLowerCase();
18
+ let currentIndex = 0;
19
+ while(currentIndex < label.length){
20
+ const matchIndex = lowerLabel.indexOf(lowerQuery, currentIndex);
21
+ if (-1 === matchIndex) {
22
+ parts.push({
23
+ text: label.slice(currentIndex),
24
+ highlighted: false
25
+ });
26
+ break;
27
+ }
28
+ if (matchIndex > currentIndex) parts.push({
29
+ text: label.slice(currentIndex, matchIndex),
30
+ highlighted: false
31
+ });
32
+ parts.push({
33
+ text: label.slice(matchIndex, matchIndex + query.length),
34
+ highlighted: true
35
+ });
36
+ currentIndex = matchIndex + query.length;
37
+ }
38
+ return parts;
39
+ }
40
+ function ComboBoxComponent({ defaultValue = '', value: controlled, label, options = [], onChange, required = false, disabled, name, id: idProp, hint, error, placeholder, onInputChange, emptyMessage = 'No results found', className, ...props }, ref) {
41
+ const id = useId(idProp);
42
+ const [value, setValue] = useControlled({
43
+ controlled,
44
+ default: defaultValue,
45
+ onChange
46
+ });
47
+ const [query, setQuery] = useState('');
48
+ const selectedOption = useMemo(()=>options.find((option)=>option.data === value), [
49
+ options,
50
+ value
51
+ ]);
52
+ const filteredOptions = useMemo(()=>{
53
+ if (!query) return options;
54
+ const normalizedQuery = query.toLowerCase();
55
+ return options.filter((option)=>option.label.toLowerCase().includes(normalizedQuery));
56
+ }, [
57
+ options,
58
+ query
59
+ ]);
60
+ function handleSelect(option) {
61
+ if (!option) return;
62
+ setValue(option.data);
63
+ setQuery('');
64
+ }
65
+ function handleInputChange(inputValue) {
66
+ setQuery(inputValue);
67
+ onInputChange?.(inputValue);
68
+ }
69
+ const inputClasses = classnames('fr-combobox-input', {
70
+ 'fr-required': required,
71
+ 'fr-disabled': disabled,
72
+ 'fr-invalid': error,
73
+ 'fr-empty': !query && !selectedOption
74
+ });
75
+ return /*#__PURE__*/ jsx(Combobox, {
76
+ value: selectedOption ?? null,
77
+ onChange: handleSelect,
78
+ disabled: disabled,
79
+ immediate: true,
80
+ children: /*#__PURE__*/ jsxs("div", {
81
+ className: classnames('fr-combobox-wrapper', {
82
+ 'fr-disabled': disabled,
83
+ 'fr-invalid': error,
84
+ 'fr-required': required
85
+ }, className),
86
+ ref: ref,
87
+ ...props,
88
+ children: [
89
+ /*#__PURE__*/ jsx("label", {
90
+ className: classnames('fr-combobox-label'),
91
+ htmlFor: id,
92
+ children: label || ''
93
+ }),
94
+ /*#__PURE__*/ jsxs("div", {
95
+ className: "fr-combobox-control",
96
+ children: [
97
+ /*#__PURE__*/ jsx(ComboboxInput, {
98
+ className: inputClasses,
99
+ id: id,
100
+ name: name,
101
+ required: required,
102
+ disabled: disabled,
103
+ placeholder: placeholder,
104
+ autoComplete: "off",
105
+ displayValue: (option)=>option?.label ?? '',
106
+ onChange: (event)=>handleInputChange(event.target.value)
107
+ }),
108
+ /*#__PURE__*/ jsx(ComboboxButton, {
109
+ className: "fr-combobox-button",
110
+ "aria-label": "Optionen anzeigen"
111
+ })
112
+ ]
113
+ }),
114
+ /*#__PURE__*/ jsxs(ComboboxOptions, {
115
+ className: classnames('fr-combobox-options-container', 'fr-combobox-options'),
116
+ as: "ul",
117
+ anchor: {
118
+ to: 'bottom start',
119
+ gap: 20,
120
+ padding: 8
121
+ },
122
+ children: [
123
+ 0 === filteredOptions.length && /*#__PURE__*/ jsx("li", {
124
+ className: "fr-combobox-option fr-combobox-empty",
125
+ children: /*#__PURE__*/ jsx("span", {
126
+ className: "fr-combobox-option-content",
127
+ children: emptyMessage
128
+ })
129
+ }),
130
+ filteredOptions.map((option)=>/*#__PURE__*/ jsx(ComboboxOption, {
131
+ value: option,
132
+ disabled: option.disabled,
133
+ as: "li",
134
+ className: ({ focus, selected, disabled: isDisabled })=>classnames('fr-combobox-option', {
135
+ 'fr-selected': selected,
136
+ 'fr-active': focus,
137
+ 'fr-disabled': isDisabled
138
+ }),
139
+ children: /*#__PURE__*/ jsx("span", {
140
+ className: "fr-combobox-option-content",
141
+ "data-testid": "fr-combobox-option-content",
142
+ children: getHighlightedLabel(option.label, query).map((part, index)=>/*#__PURE__*/ jsx("span", {
143
+ className: classnames({
144
+ 'fr-combobox-highlight': part.highlighted
145
+ }),
146
+ children: part.text
147
+ }, `${part.text}-${index}`))
148
+ })
149
+ }, String(option.data)))
150
+ ]
151
+ }),
152
+ hint && !error && /*#__PURE__*/ jsx("div", {
153
+ className: "fr-combobox-description",
154
+ children: hint
155
+ }),
156
+ error && /*#__PURE__*/ jsx("div", {
157
+ className: "fr-combobox-invalid",
158
+ children: error
159
+ })
160
+ ]
161
+ })
162
+ });
163
+ }
164
+ const ComboBox = /*#__PURE__*/ forwardRef(ComboBoxComponent);
165
+ export { ComboBox };
package/dist/index.d.ts CHANGED
@@ -45,6 +45,7 @@ export { type TabItemAppearance, Tabs, type TabsConfiguration, type TabsProps, t
45
45
  export { TabPane, type TabPaneProps } from './components/tab/tab-pane/TabPane';
46
46
  export { TableHeaderCell, type TableHeaderCellProps, } from './components/table-header-cell/TableHeaderCell';
47
47
  export { Checkbox, type CheckboxProps } from './forms/checkbox/Checkbox';
48
+ export { ComboBox, type ComboBoxOptionType, type ComboBoxProps, } from './forms/combobox/ComboBox';
48
49
  export { RadioButton, type RadioButtonProps as RadioProps, } from './forms/radio/RadioButton';
49
50
  export { RadioGroup, type RadioGroupProps } from './forms/radio/RadioGroup';
50
51
  export { Segment, type SegmentConfiguration, type SegmentProps, } from './forms/segment/Segment';
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ import { Tabs } from "./components/tab/Tabs.js";
45
45
  import { TabPane } from "./components/tab/tab-pane/TabPane.js";
46
46
  import { TableHeaderCell } from "./components/table-header-cell/TableHeaderCell.js";
47
47
  import { Checkbox } from "./forms/checkbox/Checkbox.js";
48
+ import { ComboBox } from "./forms/combobox/ComboBox.js";
48
49
  import { RadioButton } from "./forms/radio/RadioButton.js";
49
50
  import { RadioGroup } from "./forms/radio/RadioGroup.js";
50
51
  import { Segment } from "./forms/segment/Segment.js";
@@ -56,4 +57,4 @@ import { Switch } from "./forms/switch/Switch.js";
56
57
  import { TextArea } from "./forms/text-area/TextArea.js";
57
58
  import { TextInput } from "./forms/text-input/TextInput.js";
58
59
  import { TimePicker } from "./forms/time-picker/TimePicker.js";
59
- export { Accordion, AccordionHeader, AccordionItem, AccordionItemBody, AccordionItemHeader, AlertModal, BottomSheet, Breadcrumb, Button, Card, CardBody, CardHeader, CardNotification, Checkbox, Chip, ChipContainer, ChipType, ConfirmModal, CustomModal, ImageGallery, ImageGalleryContent, ImageGallerySwiper, ImageGalleryThumbsSwiper, Legend, LoadingIndicator, MobileFlyout, MobileFlyoutItem, MobileFlyoutPage, Pagination, Popover, PopoverMenu, PopoverMenuContext, PopoverMenuItem, Progress, Prompt, RadioButton, RadioGroup, SearchInput, SearchSuggestion, Segment, SegmentControl, Select, SelectOption, Slider, Snackbar, SnackbarProvider, StepHorizontal, StepVertical, StepperHorizontal, StepperVertical, Switch, TabPane, TableHeaderCell, Tabs, TextArea, TextInput, TimePicker, Tooltip, addSnackbar, useSnackbar };
60
+ export { Accordion, AccordionHeader, AccordionItem, AccordionItemBody, AccordionItemHeader, AlertModal, BottomSheet, Breadcrumb, Button, Card, CardBody, CardHeader, CardNotification, Checkbox, Chip, ChipContainer, ChipType, ComboBox, ConfirmModal, CustomModal, ImageGallery, ImageGalleryContent, ImageGallerySwiper, ImageGalleryThumbsSwiper, Legend, LoadingIndicator, MobileFlyout, MobileFlyoutItem, MobileFlyoutPage, Pagination, Popover, PopoverMenu, PopoverMenuContext, PopoverMenuItem, Progress, Prompt, RadioButton, RadioGroup, SearchInput, SearchSuggestion, Segment, SegmentControl, Select, SelectOption, Slider, Snackbar, SnackbarProvider, StepHorizontal, StepVertical, StepperHorizontal, StepperVertical, Switch, TabPane, TableHeaderCell, Tabs, TextArea, TextInput, TimePicker, Tooltip, addSnackbar, useSnackbar };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@festo-ui/react",
3
- "version": "9.0.1-dev.760",
3
+ "version": "9.0.1-dev.762",
4
4
  "author": "Festo UI (styleguide@festo.com)",
5
5
  "copyright": "Copyright (c) 2025 Festo SE & Co. KG. All rights reserved.",
6
6
  "license": "apache-2.0",
@@ -34,6 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@festo-ui/react-icons": "*",
37
+ "@headlessui/react": "^2.2.9",
37
38
  "@popperjs/core": "^2.11.0",
38
39
  "classnames": "^2.2.6",
39
40
  "react-popper": "^2.3.0",