@festo-ui/react 10.1.0 → 10.1.1-dev.919

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.
@@ -1,9 +1,5 @@
1
1
  import './Pagination.scss';
2
- export declare enum PaginationType {
3
- Simple = "SIMPLE",
4
- Numeric = "NUMERIC",
5
- Dots = "DOTS"
6
- }
2
+ export type PaginationType = 'SIMPLE' | 'NUMERIC' | 'DOTS';
7
3
  export interface PaginationProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'onChange'> {
8
4
  readonly type?: PaginationType;
9
5
  readonly onChange?: (page: number, event: React.MouseEvent<HTMLButtonElement>) => void;
@@ -2,13 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import "./Pagination.css";
3
3
  import classnames from "classnames";
4
4
  import { forwardRef, useEffect, useState } from "react";
5
- var Pagination_PaginationType = /*#__PURE__*/ function(PaginationType) {
6
- PaginationType["Simple"] = "SIMPLE";
7
- PaginationType["Numeric"] = "NUMERIC";
8
- PaginationType["Dots"] = "DOTS";
9
- return PaginationType;
10
- }({});
11
- const Pagination = /*#__PURE__*/ forwardRef(({ pageMax, onChange, pageCurrent, defaultPageCurrent = 1, type = "NUMERIC", className, ...props }, ref)=>{
5
+ const Pagination = /*#__PURE__*/ forwardRef(({ pageMax, onChange, pageCurrent, defaultPageCurrent = 1, type = 'NUMERIC', className, ...props }, ref)=>{
12
6
  const controlled = void 0 !== pageCurrent;
13
7
  const dotArray = Array.from(new Array(pageMax).keys());
14
8
  const [innerPageCurrent, setInnerPageCurrent] = useState(controlled ? pageCurrent : defaultPageCurrent);
@@ -40,7 +34,7 @@ const Pagination = /*#__PURE__*/ forwardRef(({ pageMax, onChange, pageCurrent, d
40
34
  }
41
35
  return /*#__PURE__*/ jsxs(Fragment, {
42
36
  children: [
43
- "DOTS" !== type && /*#__PURE__*/ jsxs("div", {
37
+ 'DOTS' !== type && /*#__PURE__*/ jsxs("div", {
44
38
  ...props,
45
39
  className: classnames('fwe-pagination', {
46
40
  'fwe-d-none': pageMax < 2
@@ -55,7 +49,7 @@ const Pagination = /*#__PURE__*/ forwardRef(({ pageMax, onChange, pageCurrent, d
55
49
  type: "button",
56
50
  "aria-label": "navigate-btn-down"
57
51
  }),
58
- "NUMERIC" === type && /*#__PURE__*/ jsxs(Fragment, {
52
+ 'NUMERIC' === type && /*#__PURE__*/ jsxs(Fragment, {
59
53
  children: [
60
54
  /*#__PURE__*/ jsx("span", {
61
55
  className: "fwe-page-current",
@@ -70,7 +64,7 @@ const Pagination = /*#__PURE__*/ forwardRef(({ pageMax, onChange, pageCurrent, d
70
64
  /*#__PURE__*/ jsx("button", {
71
65
  className: classnames('fwe-navigate-btn-up', {
72
66
  'fwe-disabled': innerPageCurrent >= pageMax,
73
- 'fwe-ml-4': "SIMPLE" === type
67
+ 'fwe-ml-4': 'SIMPLE' === type
74
68
  }),
75
69
  onClick: onBtnUp,
76
70
  type: "button",
@@ -78,7 +72,7 @@ const Pagination = /*#__PURE__*/ forwardRef(({ pageMax, onChange, pageCurrent, d
78
72
  })
79
73
  ]
80
74
  }),
81
- "DOTS" === type && /*#__PURE__*/ jsx("div", {
75
+ 'DOTS' === type && /*#__PURE__*/ jsx("div", {
82
76
  ...props,
83
77
  className: classnames({
84
78
  'fwe-d-none': pageMax < 2
@@ -101,4 +95,4 @@ const Pagination = /*#__PURE__*/ forwardRef(({ pageMax, onChange, pageCurrent, d
101
95
  });
102
96
  });
103
97
  Pagination.displayName = 'Pagination';
104
- export { Pagination, Pagination_PaginationType as PaginationType };
98
+ export { Pagination };
@@ -54,16 +54,23 @@ const Popover = /*#__PURE__*/ forwardRef(({ children, className, style, containe
54
54
  children: [
55
55
  /*#__PURE__*/ jsx("div", {
56
56
  className: className,
57
- style: style,
57
+ style: {
58
+ width: 'fit-content',
59
+ height: 'fit-content',
60
+ ...style
61
+ },
58
62
  ref: (node)=>{
59
63
  refs.setReference(node);
60
64
  if ('function' == typeof handleRef) handleRef(node);
61
65
  },
62
66
  ...getReferenceProps({
63
67
  ...props,
64
- onClick: (event)=>{
65
- if (stopPropagation) event.stopPropagation();
66
- onTriggerClick?.(event);
68
+ onClick: (e)=>{
69
+ if (stopPropagation) {
70
+ e.preventDefault();
71
+ e.stopPropagation();
72
+ }
73
+ onTriggerClick?.(e);
67
74
  }
68
75
  }),
69
76
  children: children
@@ -2,6 +2,13 @@
2
2
  height: 40px;
3
3
  }
4
4
 
5
+ .fwe-search-suggestions {
6
+ top: unset;
7
+ left: unset;
8
+ right: unset;
9
+ position: static;
10
+ }
11
+
5
12
  .fr-search-input-clear-button {
6
13
  background: none;
7
14
  border: none;
@@ -11,3 +18,8 @@
11
18
  display: flex;
12
19
  }
13
20
 
21
+ .fr-search-result-highlight {
22
+ font-weight: unset;
23
+ color: var(--fwe-hero, #0091dc);
24
+ }
25
+
@@ -1,13 +1,25 @@
1
1
  import './SearchInput.scss';
2
- import { type ComponentPropsWithoutRef } from 'react';
3
- import type { SearchSuggestion } from './SearchSuggestion';
4
- export interface SearchInputProps extends Omit<ComponentPropsWithoutRef<'input'>, 'onChange' | 'value' | 'defaultValue'> {
2
+ import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
3
+ export interface SearchInputProps extends Omit<ComponentPropsWithoutRef<'input'>, 'onChange' | 'value' | 'defaultValue' | 'children'> {
4
+ /** Placeholder text shown in the input. */
5
5
  readonly label?: string;
6
- readonly defaultValue?: string;
6
+ /** Controlled value. */
7
7
  readonly value?: string;
8
- readonly suggestions?: SearchSuggestion[];
8
+ /** Initial value for uncontrolled usage. */
9
+ readonly defaultValue?: string;
10
+ /** Debounce time in milliseconds before `onChange` fires. Default: 300. */
11
+ readonly debounceTime?: number;
12
+ /**
13
+ * Called after the debounce interval when the user types.
14
+ * Use this to trigger a search and update the children (suggestions).
15
+ */
9
16
  readonly onChange?: (value: string) => void;
10
- readonly onKeyboardNavigate?: (value: string) => void;
17
+ /** Called when the user presses Enter or selects a suggestion. */
11
18
  readonly onSearch?: (value: string) => void;
19
+ /**
20
+ * Render search suggestions as children.
21
+ * Use `<SearchInputOption>` for each item.
22
+ */
23
+ readonly children?: ReactNode;
12
24
  }
13
25
  export declare const SearchInput: (props: SearchInputProps & import("react").RefAttributes<HTMLDivElement>) => React.ReactElement | null;
@@ -1,64 +1,88 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import "./SearchInput.css";
3
+ import { Combobox, ComboboxButton, ComboboxInput, ComboboxOptions } from "@headlessui/react";
3
4
  import classnames from "classnames";
4
- import { forwardRef, useRef } from "react";
5
- import { useForkRef } from "../../utils/useForkRef.js";
6
- import { useOnClickOutside } from "../../utils/useOnClickOutside.js";
7
- import { ClearButton } from "./ClearButton.js";
8
- import { useSearchInput } from "./useSearchInput.js";
9
- const SearchInput = /*#__PURE__*/ forwardRef(({ defaultValue, disabled, label, value, suggestions, onChange, onSearch, onKeyboardNavigate, className, ...props }, ref)=>{
10
- const inputRef = useRef(null);
11
- const containerRef = useRef(null);
12
- const handleRef = useForkRef(ref, containerRef);
13
- const cappedSuggestions = suggestions?.slice(0, 10) ?? [];
14
- const { handleFocus, handleInput, handleClearQuery, handleSuggestionClick, handleOutsideClick, handleKeyDown, hideSuggestionList, selectedSuggestionIndex, innerValue } = useSearchInput(inputRef, cappedSuggestions, value, defaultValue, onChange, onSearch, onKeyboardNavigate);
15
- useOnClickOutside(containerRef, handleOutsideClick);
16
- return /*#__PURE__*/ jsxs("div", {
17
- className: classnames('fwe-search-input', className),
18
- ref: handleRef,
19
- children: [
20
- /*#__PURE__*/ jsx("input", {
21
- ref: inputRef,
22
- disabled: disabled,
23
- placeholder: label,
24
- onFocus: handleFocus,
25
- type: "search",
26
- "aria-label": "Search",
27
- onInput: handleInput,
28
- onKeyDown: handleKeyDown,
29
- value: innerValue,
30
- ...props
31
- }),
32
- /*#__PURE__*/ jsx("div", {
33
- className: "fwe-search-icon"
34
- }),
35
- /*#__PURE__*/ jsx(ClearButton, {
36
- onClick: handleClearQuery
37
- }),
38
- Boolean(suggestions?.length) && !hideSuggestionList && /*#__PURE__*/ jsxs("div", {
39
- className: "fwe-search-suggestions",
40
- role: "listbox",
41
- children: [
42
- cappedSuggestions.map((suggestion, i)=>/*#__PURE__*/ jsx("div", {
43
- role: "option",
44
- tabIndex: -1,
45
- "aria-label": suggestion.value,
46
- "aria-selected": selectedSuggestionIndex === i,
47
- onClick: ()=>handleSuggestionClick(suggestion),
48
- className: `fwe-search-suggestion ${selectedSuggestionIndex === i ? 'fwe-selected' : ''}`,
49
- children: /*#__PURE__*/ jsx("div", {
50
- dangerouslySetInnerHTML: {
51
- __html: suggestion.template
52
- }
53
- })
54
- }, suggestion.value)),
55
- suggestions && suggestions.length > 10 && /*#__PURE__*/ jsx("div", {
56
- className: "fwe-ml-xxs",
57
- children: "..."
58
- })
59
- ]
60
- })
61
- ]
5
+ import { Children, forwardRef, useCallback, useRef } from "react";
6
+ import { useControlled } from "../../utils/useControlled.js";
7
+ const SearchInput = /*#__PURE__*/ forwardRef(({ defaultValue, disabled, label, value: controlledValue, debounceTime = 0, onChange, onSearch, children, className, ...props }, ref)=>{
8
+ const [innerValue, setInnerValue] = useControlled({
9
+ controlled: controlledValue,
10
+ default: defaultValue ?? ''
11
+ });
12
+ const debounceRef = useRef(null);
13
+ const debouncedOnChange = useCallback((val)=>{
14
+ if (debounceRef.current) clearTimeout(debounceRef.current);
15
+ debounceRef.current = setTimeout(()=>{
16
+ onChange?.(val);
17
+ }, debounceTime);
18
+ }, [
19
+ onChange,
20
+ debounceTime
21
+ ]);
22
+ function handleInputChange(event) {
23
+ const val = event.target.value;
24
+ setInnerValue(val);
25
+ debouncedOnChange(val);
26
+ }
27
+ function handleSelect(val) {
28
+ if (null == val) return;
29
+ setInnerValue(val);
30
+ onSearch?.(val);
31
+ }
32
+ function handleClear() {
33
+ setInnerValue('');
34
+ if (debounceRef.current) clearTimeout(debounceRef.current);
35
+ onChange?.('');
36
+ onSearch?.('');
37
+ }
38
+ function handleKeyDown(event) {
39
+ if ('Enter' === event.key && innerValue) onSearch?.(innerValue);
40
+ if ('Escape' === event.key) handleClear();
41
+ }
42
+ const hasChildren = Children.count(children) > 0;
43
+ return /*#__PURE__*/ jsx(Combobox, {
44
+ value: innerValue,
45
+ onChange: handleSelect,
46
+ disabled: disabled,
47
+ immediate: true,
48
+ children: /*#__PURE__*/ jsxs("div", {
49
+ className: classnames('fwe-search-input', className),
50
+ ref: ref,
51
+ children: [
52
+ /*#__PURE__*/ jsx(ComboboxInput, {
53
+ disabled: disabled,
54
+ placeholder: label,
55
+ type: "search",
56
+ "aria-label": label ?? 'Search',
57
+ value: innerValue,
58
+ onChange: handleInputChange,
59
+ onKeyDown: handleKeyDown,
60
+ autoComplete: "off",
61
+ ...props
62
+ }),
63
+ /*#__PURE__*/ jsx("div", {
64
+ className: "fwe-search-icon"
65
+ }),
66
+ /*#__PURE__*/ jsx(ComboboxButton, {
67
+ className: "fwe-clear-icon fr-search-input-clear-button",
68
+ "aria-label": "Clear",
69
+ onClick: handleClear
70
+ }),
71
+ hasChildren && /*#__PURE__*/ jsx(ComboboxOptions, {
72
+ className: "fwe-search-suggestions",
73
+ as: "div",
74
+ portal: false,
75
+ anchor: {
76
+ to: 'bottom start',
77
+ gap: 4
78
+ },
79
+ style: {
80
+ minWidth: 'var(--input-width)'
81
+ },
82
+ children: children
83
+ })
84
+ ]
85
+ })
62
86
  });
63
87
  });
64
88
  SearchInput.displayName = 'SearchInput';
@@ -0,0 +1,5 @@
1
+ import { type ComboboxOptionProps } from '@headlessui/react';
2
+ export interface SearchInputOptionProps extends Omit<ComboboxOptionProps, 'as'> {
3
+ readonly value: string;
4
+ }
5
+ export declare const SearchInputOption: (props: SearchInputOptionProps & import("react").RefAttributes<HTMLDivElement>) => React.ReactElement | null;
@@ -0,0 +1,18 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { ComboboxOption } from "@headlessui/react";
3
+ import classnames from "classnames";
4
+ import { forwardRef } from "react";
5
+ function SearchInputOptionComponent({ children, className, ...props }, ref) {
6
+ return /*#__PURE__*/ jsx(ComboboxOption, {
7
+ ref: ref,
8
+ as: "div",
9
+ className: ({ focus, selected })=>classnames('fwe-search-suggestion', className, {
10
+ 'fwe-selected': focus || selected
11
+ }),
12
+ ...props,
13
+ children: children
14
+ });
15
+ }
16
+ const SearchInputOption = /*#__PURE__*/ forwardRef(SearchInputOptionComponent);
17
+ SearchInputOption.displayName = 'SearchInputOption';
18
+ export { SearchInputOption };
@@ -0,0 +1,8 @@
1
+ import type { ComponentPropsWithoutRef } from 'react';
2
+ export interface SearchResultProps extends ComponentPropsWithoutRef<'span'> {
3
+ /** The full label text to display. */
4
+ readonly label: string;
5
+ /** The current search query — matching parts will be highlighted. */
6
+ readonly query?: string;
7
+ }
8
+ export declare const SearchResult: (props: SearchResultProps & import("react").RefAttributes<HTMLSpanElement>) => React.ReactElement | null;
@@ -0,0 +1,48 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import classnames from "classnames";
3
+ import { forwardRef } from "react";
4
+ function getHighlightedParts(label, query) {
5
+ if (!query) return [
6
+ {
7
+ text: label,
8
+ highlighted: false
9
+ }
10
+ ];
11
+ const parts = [];
12
+ const lowerLabel = label.toLowerCase();
13
+ const lowerQuery = query.toLowerCase();
14
+ let currentIndex = 0;
15
+ while(currentIndex < label.length){
16
+ const matchIndex = lowerLabel.indexOf(lowerQuery, currentIndex);
17
+ if (-1 === matchIndex) {
18
+ parts.push({
19
+ text: label.slice(currentIndex),
20
+ highlighted: false
21
+ });
22
+ break;
23
+ }
24
+ if (matchIndex > currentIndex) parts.push({
25
+ text: label.slice(currentIndex, matchIndex),
26
+ highlighted: false
27
+ });
28
+ parts.push({
29
+ text: label.slice(matchIndex, matchIndex + query.length),
30
+ highlighted: true
31
+ });
32
+ currentIndex = matchIndex + query.length;
33
+ }
34
+ return parts;
35
+ }
36
+ const SearchResult = /*#__PURE__*/ forwardRef(({ label, query = '', className, ...props }, ref)=>/*#__PURE__*/ jsx("span", {
37
+ ref: ref,
38
+ className: classnames('fr-search-result', className),
39
+ ...props,
40
+ children: getHighlightedParts(label, query).map((part, index)=>/*#__PURE__*/ jsx("span", {
41
+ className: classnames({
42
+ 'fr-search-result-highlight': part.highlighted
43
+ }),
44
+ children: part.text
45
+ }, `${part.text}-${index}`))
46
+ }));
47
+ SearchResult.displayName = 'SearchResult';
48
+ export { SearchResult };
@@ -7,7 +7,7 @@ import { IconWrapper } from "../icon-wrapper/IconWrapper.js";
7
7
  import { useTabScroll } from "./useTabScroll.js";
8
8
  let nextId = 0;
9
9
  const Tabs = /*#__PURE__*/ forwardRef(({ config, children, className, onChange, viewType = 'responsive', showDivider = false, ...props }, ref)=>{
10
- const [useCompactDensity, setCompactDensity] = useState(true);
10
+ const [useCompactDensity, setUseCompactDensity] = useState(true);
11
11
  const componentId = useRef(`tabs-${++nextId}`);
12
12
  const elRef = useRef(null);
13
13
  const combinedRef = useForkRef(ref, elRef);
@@ -20,7 +20,7 @@ const Tabs = /*#__PURE__*/ forwardRef(({ config, children, className, onChange,
20
20
  if ('' === activeId || element.props.active) activeId = `${componentId.current}-tab-panel-${i}`;
21
21
  }
22
22
  });
23
- const [currentId, setId] = useState(activeId);
23
+ const [currentId, setCurrentId] = useState(activeId);
24
24
  const innerChildren = react.Children.map(children, (element, i)=>{
25
25
  if (/*#__PURE__*/ isValidElement(element)) {
26
26
  const newId = `${componentId.current}-tab-panel-${i}`;
@@ -42,16 +42,16 @@ const Tabs = /*#__PURE__*/ forwardRef(({ config, children, className, onChange,
42
42
  const { current } = scrollArea;
43
43
  if (current) {
44
44
  const initialWidth = current.offsetWidth;
45
- if (initialWidth > 768) setCompactDensity(false);
45
+ if (initialWidth > 768) setUseCompactDensity(false);
46
46
  observer.current = new ResizeObserver(()=>{
47
47
  const width = current.offsetWidth;
48
- if (width > 768 && useCompactDensity) setCompactDensity(false);
49
- else if (width <= 768 && !useCompactDensity) setCompactDensity(true);
48
+ if (width > 768 && useCompactDensity) setUseCompactDensity(false);
49
+ else if (width <= 768 && !useCompactDensity) setUseCompactDensity(true);
50
50
  });
51
- if (current) observer.current.observe(current);
51
+ observer.current.observe(current);
52
52
  }
53
53
  return ()=>{
54
- if (current && observer && observer.current) observer.current.unobserve(current);
54
+ if (current && observer?.current) observer.current.unobserve(current);
55
55
  };
56
56
  }, [
57
57
  useCompactDensity
@@ -62,7 +62,7 @@ const Tabs = /*#__PURE__*/ forwardRef(({ config, children, className, onChange,
62
62
  previous: currentId,
63
63
  current: id
64
64
  });
65
- setId(id);
65
+ setCurrentId(id);
66
66
  };
67
67
  function handleClick(e, index, id) {
68
68
  handleTabScroll(e, index);
@@ -4,7 +4,7 @@ const useTabScroll = (tabLength, componentId, refs)=>{
4
4
  const [style, setStyle] = useState({});
5
5
  const [classes, setClasses] = useState('');
6
6
  function getScrollContentStyleValue(propName) {
7
- return scrollContent.current ? window.getComputedStyle(scrollContent.current).getPropertyValue(propName) : '';
7
+ return scrollContent.current ? globalThis.getComputedStyle(scrollContent.current).getPropertyValue(propName) : '';
8
8
  }
9
9
  function calculateCurrentTranslateX() {
10
10
  const transformValue = getScrollContentStyleValue('transform');
@@ -13,7 +13,7 @@ const useTabScroll = (tabLength, componentId, refs)=>{
13
13
  if (!match) return 0;
14
14
  const matrixParams = match[1];
15
15
  const [_a, _b, _c, _d, tx, _ty] = matrixParams.split(',');
16
- return parseFloat(tx);
16
+ return Number.parseFloat(tx);
17
17
  }
18
18
  function getScrollPosition() {
19
19
  const currentTranslateX = calculateCurrentTranslateX();
@@ -42,13 +42,8 @@ const useTabScroll = (tabLength, componentId, refs)=>{
42
42
  scrollDelta
43
43
  };
44
44
  }
45
- function getAnimatingScrollPosition() {
46
- const currentTranslateX = calculateCurrentTranslateX();
47
- const scrollLeft = scrollArea.current?.scrollLeft ?? 0;
48
- return scrollLeft - currentTranslateX;
49
- }
50
45
  function stopScrollAnimation() {
51
- const currentScrollPosition = getAnimatingScrollPosition();
46
+ const currentScrollPosition = getScrollPosition();
52
47
  setClasses('');
53
48
  setStyle({
54
49
  transform: 'translateX(0px)'
@@ -75,19 +70,6 @@ const useTabScroll = (tabLength, componentId, refs)=>{
75
70
  const scrollOperation = getIncrementScrollOperation(scrollXIncrement);
76
71
  animate(scrollOperation);
77
72
  }
78
- function computeDimensions(tab) {
79
- const rootWidth = tab.offsetWidth;
80
- const rootLeft = tab.offsetLeft;
81
- const tabContent = tab.querySelector('.fr-tab-content');
82
- const contentWidth = tabContent?.offsetWidth ?? 0;
83
- const contentLeft = tabContent?.offsetLeft ?? 0;
84
- return {
85
- contentLeft: rootLeft + contentLeft,
86
- contentRight: rootLeft + contentLeft + contentWidth,
87
- rootLeft,
88
- rootRight: rootLeft + rootWidth
89
- };
90
- }
91
73
  function calculateScrollIncrement(index, nextIndex, scrollPosition, barWidth) {
92
74
  const nextTab = elRef.current?.querySelector(`#${componentId}-tab-${nextIndex}`);
93
75
  if (null == nextTab) return 0;
@@ -99,16 +81,6 @@ const useTabScroll = (tabLength, componentId, refs)=>{
99
81
  if (nextIndex < index) return Math.min(leftIncrement, 0);
100
82
  return Math.max(rightIncrement, 0);
101
83
  }
102
- function findAdjacentTabIndexClosestToEdge(index, tabDimensions, scrollPosition, barWidth) {
103
- const relativeRootLeft = tabDimensions.rootLeft - scrollPosition;
104
- const relativeRootRight = tabDimensions.rootRight - scrollPosition - barWidth;
105
- const relativeRootDelta = relativeRootLeft + relativeRootRight;
106
- const leftEdgeIsCloser = relativeRootLeft < 0 || relativeRootDelta < 0;
107
- const rightEdgeIsCloser = relativeRootRight > 0 || relativeRootDelta > 0;
108
- if (leftEdgeIsCloser) return index - 1;
109
- if (rightEdgeIsCloser) return index + 1;
110
- return -1;
111
- }
112
84
  function indexIsInRange(index) {
113
85
  return index >= 0 && index < tabLength;
114
86
  }
@@ -148,4 +120,27 @@ const useTabScroll = (tabLength, componentId, refs)=>{
148
120
  style
149
121
  ];
150
122
  };
123
+ function computeDimensions(tab) {
124
+ const rootWidth = tab.offsetWidth;
125
+ const rootLeft = tab.offsetLeft;
126
+ const tabContent = tab.querySelector('.fr-tab-content');
127
+ const contentWidth = tabContent?.offsetWidth ?? 0;
128
+ const contentLeft = tabContent?.offsetLeft ?? 0;
129
+ return {
130
+ contentLeft: rootLeft + contentLeft,
131
+ contentRight: rootLeft + contentLeft + contentWidth,
132
+ rootLeft,
133
+ rootRight: rootLeft + rootWidth
134
+ };
135
+ }
136
+ function findAdjacentTabIndexClosestToEdge(index, tabDimensions, scrollPosition, barWidth) {
137
+ const relativeRootLeft = tabDimensions.rootLeft - scrollPosition;
138
+ const relativeRootRight = tabDimensions.rootRight - scrollPosition - barWidth;
139
+ const relativeRootDelta = relativeRootLeft + relativeRootRight;
140
+ const leftEdgeIsCloser = relativeRootLeft < 0 || relativeRootDelta < 0;
141
+ const rightEdgeIsCloser = relativeRootRight > 0 || relativeRootDelta > 0;
142
+ if (leftEdgeIsCloser) return index - 1;
143
+ if (rightEdgeIsCloser) return index + 1;
144
+ return -1;
145
+ }
151
146
  export { useTabScroll };
@@ -15,11 +15,10 @@ const Segment = /*#__PURE__*/ forwardRef(({ children, legend, config, onChange,
15
15
  let useIconAndText = false;
16
16
  let tmpValue = '';
17
17
  Children.forEach(children, (child, index)=>{
18
- if (!/*#__PURE__*/ isValidElement(child)) return null;
18
+ if (!/*#__PURE__*/ isValidElement(child)) return;
19
19
  if (0 === index && null !== child.props.icon) if (innerConfig.iconOnly) useIcon = true;
20
20
  else useIconAndText = true;
21
21
  if (child.props.checked) tmpValue = child.props.value;
22
- return null;
23
22
  });
24
23
  const [value, setValue] = useControlled({
25
24
  controlled: valueProps,
@@ -1,5 +1,4 @@
1
- import type { ReactNode } from 'react';
2
- import { type Ref } from 'react';
1
+ import { type ReactNode, type Ref } from 'react';
3
2
  export interface SelectOption<T> {
4
3
  readonly data: T;
5
4
  readonly label: ReactNode;
package/dist/index.d.ts CHANGED
@@ -34,7 +34,8 @@ export { PopoverMenuItem, type PopoverMenuItemProps, } from './components/popove
34
34
  export { Tooltip, type TooltipProps, } from './components/popovers/tooltip/Tooltip';
35
35
  export { Progress, type ProgressProps } from './components/progress/Progress';
36
36
  export { SearchInput, type SearchInputProps, } from './components/search-input/SearchInput';
37
- export { SearchSuggestion } from './components/search-input/SearchSuggestion';
37
+ export { SearchInputOption, type SearchInputOptionProps, } from './components/search-input/SearchInputOption';
38
+ export { SearchResult, type SearchResultProps, } from './components/search-input/SearchResult';
38
39
  export { Snackbar, type SnackbarConfig, type SnackbarData, type SnackbarProps, } from './components/snackbar/Snackbar';
39
40
  export { addSnackbar, SnackbarProvider, type SnackbarProviderProps, } from './components/snackbar/SnackbarProvider';
40
41
  export { useSnackbar } from './components/snackbar/useSnackbar';
package/dist/index.js CHANGED
@@ -33,7 +33,8 @@ import { PopoverMenuItem } from "./components/popovers/popover-menu/popover-menu
33
33
  import { Tooltip } from "./components/popovers/tooltip/Tooltip.js";
34
34
  import { Progress } from "./components/progress/Progress.js";
35
35
  import { SearchInput } from "./components/search-input/SearchInput.js";
36
- import { SearchSuggestion } from "./components/search-input/SearchSuggestion.js";
36
+ import { SearchInputOption } from "./components/search-input/SearchInputOption.js";
37
+ import { SearchResult } from "./components/search-input/SearchResult.js";
37
38
  import { Snackbar } from "./components/snackbar/Snackbar.js";
38
39
  import { SnackbarProvider, addSnackbar } from "./components/snackbar/SnackbarProvider.js";
39
40
  import { useSnackbar } from "./components/snackbar/useSnackbar.js";
@@ -57,4 +58,4 @@ import { Switch } from "./forms/switch/Switch.js";
57
58
  import { TextArea } from "./forms/text-area/TextArea.js";
58
59
  import { TextInput } from "./forms/text-input/TextInput.js";
59
60
  import { TimePicker } from "./forms/time-picker/TimePicker.js";
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, MultiSelect, Pagination, Popover, PopoverMenu, PopoverMenuContext, PopoverMenuItem, Progress, Prompt, RadioButton, RadioGroup, SearchInput, SearchSuggestion, Segment, SegmentControl, Select, Slider, Snackbar, SnackbarProvider, StepHorizontal, StepVertical, StepperHorizontal, StepperVertical, Switch, TabPane, TableHeaderCell, Tabs, TextArea, TextInput, TimePicker, Tooltip, addSnackbar, useSnackbar };
61
+ 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, MultiSelect, Pagination, Popover, PopoverMenu, PopoverMenuContext, PopoverMenuItem, Progress, Prompt, RadioButton, RadioGroup, SearchInput, SearchInputOption, SearchResult, Segment, SegmentControl, Select, Slider, Snackbar, SnackbarProvider, StepHorizontal, StepVertical, StepperHorizontal, StepperVertical, Switch, TabPane, TableHeaderCell, Tabs, TextArea, TextInput, TimePicker, Tooltip, addSnackbar, useSnackbar };
@@ -0,0 +1,20 @@
1
+ # @festo-ui/react — AI Agent Documentation
2
+
3
+ This documentation describes the React component library `@festo-ui/react` of the Festo Design System. It is optimized for AI agents that should use this library in projects.
4
+
5
+ ## Overview
6
+
7
+ | File | Content |
8
+ |------|--------|
9
+ | [installation.md](./installation.md) | Installation, setup, imports |
10
+ | [components.md](./components.md) | All UI components (Accordion, Button, Card, Modal, etc.) |
11
+ | [forms.md](./forms.md) | All form components (TextInput, Select, Checkbox, etc.) |
12
+ | [patterns.md](./patterns.md) | Common patterns: Controlled/Uncontrolled, FormData, Styling |
13
+
14
+ ## Quick Reference
15
+
16
+ **Package:** `@festo-ui/react`
17
+ **CSS:** `@festo-ui/react/index.css`
18
+ **License:** Apache-2.0
19
+ **Peer Dependencies:** React 18+
20
+ **CSS Prefix:** `fwe-` (Festo Web Essentials)