@redis-ui/components 49.0.2 → 49.1.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/README.md CHANGED
@@ -29,11 +29,11 @@ You also need to render the `CommonStyles` global style component inside the the
29
29
 
30
30
  ```tsx
31
31
  import { ThemeProvider } from 'styled-components';
32
- import { themesRebrand, CommonStyles } from '@redis-ui/styles';
32
+ import { themes, CommonStyles } from '@redis-ui/styles';
33
33
 
34
34
  function App() {
35
35
  return (
36
- <ThemeProvider theme={themesRebrand.light}>
36
+ <ThemeProvider theme={themes.light}>
37
37
  <CommonStyles />
38
38
  {/* your app */}
39
39
  </ThemeProvider>
@@ -46,13 +46,13 @@ function App() {
46
46
  For automatic light/dark mode switching with persistence, use the `SwitchableModeThemeProvider`:
47
47
 
48
48
  ```tsx
49
- import { SwitchableModeThemeProvider, themesRebrand, CommonStyles } from '@redis-ui/styles';
49
+ import { SwitchableModeThemeProvider, themes, CommonStyles } from '@redis-ui/styles';
50
50
 
51
51
  function App() {
52
52
  return (
53
53
  <SwitchableModeThemeProvider
54
- lightTheme={themesRebrand.light}
55
- darkTheme={themesRebrand.dark}
54
+ lightTheme={themes.light}
55
+ darkTheme={themes.dark}
56
56
  storageKey="redis-ui-theme-mode"
57
57
  >
58
58
  <CommonStyles />
@@ -1,4 +1,4 @@
1
- const require_index = require("../../../node_modules/@radix-ui/react-compose-refs/dist/index.cjs");
1
+ const require_index = require("../../../packages/components/node_modules/@radix-ui/react-compose-refs/dist/index.cjs");
2
2
  const require_react_utils = require("../../../Helpers/react.utils.cjs");
3
3
  const require_Drawer_context = require("../../Drawer.context.cjs");
4
4
  const require_useJitterFix = require("./hooks/useJitterFix.cjs");
@@ -1,4 +1,4 @@
1
- import { useComposedRefs } from "../../../node_modules/@radix-ui/react-compose-refs/dist/index.js";
1
+ import { useComposedRefs } from "../../../packages/components/node_modules/@radix-ui/react-compose-refs/dist/index.js";
2
2
  import { handlePreventDefault } from "../../../Helpers/react.utils.js";
3
3
  import { useDrawerContext } from "../../Drawer.context.js";
4
4
  import { useJitterFix } from "./hooks/useJitterFix.js";
@@ -1,4 +1,4 @@
1
- const require_index = require("../../../node_modules/@radix-ui/react-compose-refs/dist/index.cjs");
1
+ const require_index = require("../../../packages/components/node_modules/@radix-ui/react-compose-refs/dist/index.cjs");
2
2
  const require_react_utils = require("../../../Helpers/react.utils.cjs");
3
3
  let react = require("react");
4
4
  //#region src/Inputs/components/Context/InputValue.context.tsx
@@ -1,4 +1,4 @@
1
- import { useComposedRefs } from "../../../node_modules/@radix-ui/react-compose-refs/dist/index.js";
1
+ import { useComposedRefs } from "../../../packages/components/node_modules/@radix-ui/react-compose-refs/dist/index.js";
2
2
  import { combineHandlers } from "../../../Helpers/react.utils.js";
3
3
  import { createContext, useContext } from "react";
4
4
  //#region src/Inputs/components/Context/InputValue.context.tsx
@@ -1,4 +1,4 @@
1
- const require_index = require("../../../../../node_modules/@radix-ui/react-compose-refs/dist/index.cjs");
1
+ const require_index = require("../../../../../packages/components/node_modules/@radix-ui/react-compose-refs/dist/index.cjs");
2
2
  const require_react_utils = require("../../../../../Helpers/react.utils.cjs");
3
3
  const require_index$1 = require("../../../../../node_modules/@radix-ui/react-dialog/dist/index.cjs");
4
4
  const require_Compose_style = require("./Compose.style.cjs");
@@ -1,4 +1,4 @@
1
- import { useComposedRefs } from "../../../../../node_modules/@radix-ui/react-compose-refs/dist/index.js";
1
+ import { useComposedRefs } from "../../../../../packages/components/node_modules/@radix-ui/react-compose-refs/dist/index.js";
2
2
  import { handlePreventDefault, handleStopPropagation } from "../../../../../Helpers/react.utils.js";
3
3
  import { Portal } from "../../../../../node_modules/@radix-ui/react-dialog/dist/index.js";
4
4
  import { Container, ModalOverlay } from "./Compose.style.js";
@@ -1,4 +1,4 @@
1
- const require_index = require("../../node_modules/@radix-ui/react-compose-refs/dist/index.cjs");
1
+ const require_index = require("../../packages/components/node_modules/@radix-ui/react-compose-refs/dist/index.cjs");
2
2
  let react = require("react");
3
3
  //#region src/Typography/hooks/useEllipsisTooltip.tsx
4
4
  var useEllipsisTooltip = ({ tooltipOnEllipsis, onMouseEnter, onMouseLeave, title }, ref) => {
@@ -1,4 +1,4 @@
1
- import { useComposedRefs } from "../../node_modules/@radix-ui/react-compose-refs/dist/index.js";
1
+ import { useComposedRefs } from "../../packages/components/node_modules/@radix-ui/react-compose-refs/dist/index.js";
2
2
  import { useCallback, useRef, useState } from "react";
3
3
  //#region src/Typography/hooks/useEllipsisTooltip.tsx
4
4
  var useEllipsisTooltip = ({ tooltipOnEllipsis, onMouseEnter, onMouseLeave, title }, ref) => {
@@ -0,0 +1,30 @@
1
+ const require_runtime = require("../../../../../../_virtual/_rolldown/runtime.cjs");
2
+ let react = require("react");
3
+ react = require_runtime.__toESM(react, 1);
4
+ //#region node_modules/@radix-ui/react-compose-refs/dist/index.mjs
5
+ function setRef(ref, value) {
6
+ if (typeof ref === "function") return ref(value);
7
+ else if (ref !== null && ref !== void 0) ref.current = value;
8
+ }
9
+ function composeRefs(...refs) {
10
+ return (node) => {
11
+ let hasCleanup = false;
12
+ const cleanups = refs.map((ref) => {
13
+ const cleanup = setRef(ref, node);
14
+ if (!hasCleanup && typeof cleanup == "function") hasCleanup = true;
15
+ return cleanup;
16
+ });
17
+ if (hasCleanup) return () => {
18
+ for (let i = 0; i < cleanups.length; i++) {
19
+ const cleanup = cleanups[i];
20
+ if (typeof cleanup == "function") cleanup();
21
+ else setRef(refs[i], null);
22
+ }
23
+ };
24
+ };
25
+ }
26
+ function useComposedRefs(...refs) {
27
+ return react.useCallback(composeRefs(...refs), refs);
28
+ }
29
+ //#endregion
30
+ exports.useComposedRefs = useComposedRefs;
@@ -0,0 +1,28 @@
1
+ import * as React$1 from "react";
2
+ //#region node_modules/@radix-ui/react-compose-refs/dist/index.mjs
3
+ function setRef(ref, value) {
4
+ if (typeof ref === "function") return ref(value);
5
+ else if (ref !== null && ref !== void 0) ref.current = value;
6
+ }
7
+ function composeRefs(...refs) {
8
+ return (node) => {
9
+ let hasCleanup = false;
10
+ const cleanups = refs.map((ref) => {
11
+ const cleanup = setRef(ref, node);
12
+ if (!hasCleanup && typeof cleanup == "function") hasCleanup = true;
13
+ return cleanup;
14
+ });
15
+ if (hasCleanup) return () => {
16
+ for (let i = 0; i < cleanups.length; i++) {
17
+ const cleanup = cleanups[i];
18
+ if (typeof cleanup == "function") cleanup();
19
+ else setRef(refs[i], null);
20
+ }
21
+ };
22
+ };
23
+ }
24
+ function useComposedRefs(...refs) {
25
+ return React$1.useCallback(composeRefs(...refs), refs);
26
+ }
27
+ //#endregion
28
+ export { useComposedRefs };
@@ -1,68 +1,50 @@
1
1
  const require_runtime = require("../../../../../../_virtual/_rolldown/runtime.cjs");
2
- const require_index = require("../../../../../../node_modules/@radix-ui/react-compose-refs/dist/index.cjs");
3
- let react_jsx_runtime = require("react/jsx-runtime");
2
+ const require_index = require("../../react-compose-refs/dist/index.cjs");
4
3
  let react = require("react");
5
4
  react = require_runtime.__toESM(react, 1);
6
5
  //#region node_modules/@radix-ui/react-slot/dist/index.mjs
7
- var REACT_LAZY_TYPE = Symbol.for("react.lazy");
8
- var use = react[" use ".trim().toString()];
9
- function isPromiseLike(value) {
10
- return typeof value === "object" && value !== null && "then" in value;
11
- }
12
- function isLazyComponent(element) {
13
- return element != null && typeof element === "object" && "$$typeof" in element && element.$$typeof === REACT_LAZY_TYPE && "_payload" in element && isPromiseLike(element._payload);
14
- }
15
6
  // @__NO_SIDE_EFFECTS__
16
7
  function createSlot(ownerName) {
17
- const SlotClone = /* @__PURE__ */ createSlotClone(ownerName);
18
8
  const Slot2 = react.forwardRef((props, forwardedRef) => {
19
9
  let { children, ...slotProps } = props;
10
+ let slottableElement = null;
11
+ let hasSlottable = false;
12
+ const newChildren = [];
20
13
  if (isLazyComponent(children) && typeof use === "function") children = use(children._payload);
21
- const childrenArray = react.Children.toArray(children);
22
- const slottable = childrenArray.find(isSlottable);
23
- if (slottable) {
24
- const newElement = slottable.props.children;
25
- const newChildren = childrenArray.map((child) => {
26
- if (child === slottable) {
27
- if (react.Children.count(newElement) > 1) return react.Children.only(null);
28
- return react.isValidElement(newElement) ? newElement.props.children : null;
29
- } else return child;
30
- });
31
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlotClone, {
32
- ...slotProps,
33
- ref: forwardedRef,
34
- children: react.isValidElement(newElement) ? react.cloneElement(newElement, void 0, newChildren) : null
35
- });
36
- }
37
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SlotClone, {
38
- ...slotProps,
39
- ref: forwardedRef,
40
- children
14
+ react.Children.forEach(children, (maybeSlottable) => {
15
+ if (isSlottable(maybeSlottable)) {
16
+ hasSlottable = true;
17
+ const slottable = maybeSlottable;
18
+ let child = "child" in slottable.props ? slottable.props.child : slottable.props.children;
19
+ if (isLazyComponent(child) && typeof use === "function") child = use(child._payload);
20
+ slottableElement = getSlottableElementFromSlottable(slottable, child);
21
+ newChildren.push(slottableElement?.props?.children);
22
+ } else newChildren.push(maybeSlottable);
41
23
  });
24
+ if (slottableElement) slottableElement = react.cloneElement(slottableElement, void 0, newChildren);
25
+ else if (!hasSlottable && react.Children.count(children) === 1 && react.isValidElement(children)) slottableElement = children;
26
+ const slottableElementRef = slottableElement ? getElementRef(slottableElement) : void 0;
27
+ const composedRef = require_index.useComposedRefs(forwardedRef, slottableElementRef);
28
+ if (!slottableElement) {
29
+ if (children || children === 0) throw new Error(hasSlottable ? createSlottableError(ownerName) : createSlotError(ownerName));
30
+ return children;
31
+ }
32
+ const mergedProps = mergeProps(slotProps, slottableElement.props ?? {});
33
+ if (slottableElement.type !== react.Fragment) mergedProps.ref = forwardedRef ? composedRef : slottableElementRef;
34
+ return react.cloneElement(slottableElement, mergedProps);
42
35
  });
43
36
  Slot2.displayName = `${ownerName}.Slot`;
44
37
  return Slot2;
45
38
  }
46
- // @__NO_SIDE_EFFECTS__
47
- function createSlotClone(ownerName) {
48
- const SlotClone = react.forwardRef((props, forwardedRef) => {
49
- let { children, ...slotProps } = props;
50
- if (isLazyComponent(children) && typeof use === "function") children = use(children._payload);
51
- if (react.isValidElement(children)) {
52
- const childrenRef = getElementRef(children);
53
- const props2 = mergeProps(slotProps, children.props);
54
- if (children.type !== react.Fragment) props2.ref = forwardedRef ? require_index.composeRefs(forwardedRef, childrenRef) : childrenRef;
55
- return react.cloneElement(children, props2);
56
- }
57
- return react.Children.count(children) > 1 ? react.Children.only(null) : null;
58
- });
59
- SlotClone.displayName = `${ownerName}.SlotClone`;
60
- return SlotClone;
61
- }
62
- var SLOTTABLE_IDENTIFIER = Symbol("radix.slottable");
63
- function isSlottable(child) {
64
- return react.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
65
- }
39
+ var SLOTTABLE_IDENTIFIER = Symbol.for("radix.slottable");
40
+ var getSlottableElementFromSlottable = (slottable, child) => {
41
+ if ("child" in slottable.props) {
42
+ const child2 = slottable.props.child;
43
+ if (!react.isValidElement(child2)) return null;
44
+ return react.cloneElement(child2, void 0, slottable.props.children(child2.props.children));
45
+ }
46
+ return react.isValidElement(child) ? child : null;
47
+ };
66
48
  function mergeProps(slotProps, childProps) {
67
49
  const overrideProps = { ...childProps };
68
50
  for (const propName in childProps) {
@@ -95,5 +77,22 @@ function getElementRef(element) {
95
77
  if (mayWarn) return element.props.ref;
96
78
  return element.props.ref || element.ref;
97
79
  }
80
+ function isSlottable(child) {
81
+ return react.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
82
+ }
83
+ var REACT_LAZY_TYPE = Symbol.for("react.lazy");
84
+ function isLazyComponent(element) {
85
+ return element != null && typeof element === "object" && "$$typeof" in element && element.$$typeof === REACT_LAZY_TYPE && "_payload" in element && isPromiseLike(element._payload);
86
+ }
87
+ function isPromiseLike(value) {
88
+ return typeof value === "object" && value !== null && "then" in value;
89
+ }
90
+ var createSlotError = (ownerName) => {
91
+ return `${ownerName} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`;
92
+ };
93
+ var createSlottableError = (ownerName) => {
94
+ return `${ownerName} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`;
95
+ };
96
+ var use = react[" use ".trim().toString()];
98
97
  //#endregion
99
98
  exports.createSlot = createSlot;
@@ -1,66 +1,48 @@
1
- import { composeRefs } from "../../../../../../node_modules/@radix-ui/react-compose-refs/dist/index.js";
2
- import { jsx } from "react/jsx-runtime";
1
+ import { useComposedRefs } from "../../react-compose-refs/dist/index.js";
3
2
  import * as React$1 from "react";
4
3
  //#region node_modules/@radix-ui/react-slot/dist/index.mjs
5
- var REACT_LAZY_TYPE = Symbol.for("react.lazy");
6
- var use = React$1[" use ".trim().toString()];
7
- function isPromiseLike(value) {
8
- return typeof value === "object" && value !== null && "then" in value;
9
- }
10
- function isLazyComponent(element) {
11
- return element != null && typeof element === "object" && "$$typeof" in element && element.$$typeof === REACT_LAZY_TYPE && "_payload" in element && isPromiseLike(element._payload);
12
- }
13
4
  // @__NO_SIDE_EFFECTS__
14
5
  function createSlot(ownerName) {
15
- const SlotClone = /* @__PURE__ */ createSlotClone(ownerName);
16
6
  const Slot2 = React$1.forwardRef((props, forwardedRef) => {
17
7
  let { children, ...slotProps } = props;
8
+ let slottableElement = null;
9
+ let hasSlottable = false;
10
+ const newChildren = [];
18
11
  if (isLazyComponent(children) && typeof use === "function") children = use(children._payload);
19
- const childrenArray = React$1.Children.toArray(children);
20
- const slottable = childrenArray.find(isSlottable);
21
- if (slottable) {
22
- const newElement = slottable.props.children;
23
- const newChildren = childrenArray.map((child) => {
24
- if (child === slottable) {
25
- if (React$1.Children.count(newElement) > 1) return React$1.Children.only(null);
26
- return React$1.isValidElement(newElement) ? newElement.props.children : null;
27
- } else return child;
28
- });
29
- return /* @__PURE__ */ jsx(SlotClone, {
30
- ...slotProps,
31
- ref: forwardedRef,
32
- children: React$1.isValidElement(newElement) ? React$1.cloneElement(newElement, void 0, newChildren) : null
33
- });
34
- }
35
- return /* @__PURE__ */ jsx(SlotClone, {
36
- ...slotProps,
37
- ref: forwardedRef,
38
- children
12
+ React$1.Children.forEach(children, (maybeSlottable) => {
13
+ if (isSlottable(maybeSlottable)) {
14
+ hasSlottable = true;
15
+ const slottable = maybeSlottable;
16
+ let child = "child" in slottable.props ? slottable.props.child : slottable.props.children;
17
+ if (isLazyComponent(child) && typeof use === "function") child = use(child._payload);
18
+ slottableElement = getSlottableElementFromSlottable(slottable, child);
19
+ newChildren.push(slottableElement?.props?.children);
20
+ } else newChildren.push(maybeSlottable);
39
21
  });
22
+ if (slottableElement) slottableElement = React$1.cloneElement(slottableElement, void 0, newChildren);
23
+ else if (!hasSlottable && React$1.Children.count(children) === 1 && React$1.isValidElement(children)) slottableElement = children;
24
+ const slottableElementRef = slottableElement ? getElementRef(slottableElement) : void 0;
25
+ const composedRef = useComposedRefs(forwardedRef, slottableElementRef);
26
+ if (!slottableElement) {
27
+ if (children || children === 0) throw new Error(hasSlottable ? createSlottableError(ownerName) : createSlotError(ownerName));
28
+ return children;
29
+ }
30
+ const mergedProps = mergeProps(slotProps, slottableElement.props ?? {});
31
+ if (slottableElement.type !== React$1.Fragment) mergedProps.ref = forwardedRef ? composedRef : slottableElementRef;
32
+ return React$1.cloneElement(slottableElement, mergedProps);
40
33
  });
41
34
  Slot2.displayName = `${ownerName}.Slot`;
42
35
  return Slot2;
43
36
  }
44
- // @__NO_SIDE_EFFECTS__
45
- function createSlotClone(ownerName) {
46
- const SlotClone = React$1.forwardRef((props, forwardedRef) => {
47
- let { children, ...slotProps } = props;
48
- if (isLazyComponent(children) && typeof use === "function") children = use(children._payload);
49
- if (React$1.isValidElement(children)) {
50
- const childrenRef = getElementRef(children);
51
- const props2 = mergeProps(slotProps, children.props);
52
- if (children.type !== React$1.Fragment) props2.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef;
53
- return React$1.cloneElement(children, props2);
54
- }
55
- return React$1.Children.count(children) > 1 ? React$1.Children.only(null) : null;
56
- });
57
- SlotClone.displayName = `${ownerName}.SlotClone`;
58
- return SlotClone;
59
- }
60
- var SLOTTABLE_IDENTIFIER = Symbol("radix.slottable");
61
- function isSlottable(child) {
62
- return React$1.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
63
- }
37
+ var SLOTTABLE_IDENTIFIER = Symbol.for("radix.slottable");
38
+ var getSlottableElementFromSlottable = (slottable, child) => {
39
+ if ("child" in slottable.props) {
40
+ const child2 = slottable.props.child;
41
+ if (!React$1.isValidElement(child2)) return null;
42
+ return React$1.cloneElement(child2, void 0, slottable.props.children(child2.props.children));
43
+ }
44
+ return React$1.isValidElement(child) ? child : null;
45
+ };
64
46
  function mergeProps(slotProps, childProps) {
65
47
  const overrideProps = { ...childProps };
66
48
  for (const propName in childProps) {
@@ -93,5 +75,22 @@ function getElementRef(element) {
93
75
  if (mayWarn) return element.props.ref;
94
76
  return element.props.ref || element.ref;
95
77
  }
78
+ function isSlottable(child) {
79
+ return React$1.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
80
+ }
81
+ var REACT_LAZY_TYPE = Symbol.for("react.lazy");
82
+ function isLazyComponent(element) {
83
+ return element != null && typeof element === "object" && "$$typeof" in element && element.$$typeof === REACT_LAZY_TYPE && "_payload" in element && isPromiseLike(element._payload);
84
+ }
85
+ function isPromiseLike(value) {
86
+ return typeof value === "object" && value !== null && "then" in value;
87
+ }
88
+ var createSlotError = (ownerName) => {
89
+ return `${ownerName} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`;
90
+ };
91
+ var createSlottableError = (ownerName) => {
92
+ return `${ownerName} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`;
93
+ };
94
+ var use = React$1[" use ".trim().toString()];
96
95
  //#endregion
97
96
  export { createSlot };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@redis-ui/components",
3
3
  "license": "UNLICENSED",
4
- "version": "49.0.2",
4
+ "version": "49.1.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "repository": "git@github.com:redislabsdev/redis-ui.git",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@redis-ui/icons": "^8.0.0 || ^9.0.0 || ^10.0.0",
33
- "@redis-ui/styles": "^17.0.0 || ^18.0.0",
33
+ "@redis-ui/styles": "^17.0.0 || ^18.0.0 || ^19.0.0",
34
34
  "react": "^18.0.0 || ^19.0.0",
35
35
  "react-dom": "^18.0.0 || ^19.0.0",
36
36
  "styled-components": "^5.0.0"
@@ -120,6 +120,7 @@ Always check the relevant component reference before using a component. The refe
120
120
  - [KeyValueList](./references/KeyValueList.md)
121
121
  - [Filters](./references/Filters.md)
122
122
  - [SearchBar](./references/SearchBar.md)
123
+ - [TableHeading](./references/TableHeading.md)
123
124
 
124
125
  ### Misc
125
126
 
@@ -132,3 +133,4 @@ Always check the relevant component reference before using a component. The refe
132
133
  - [ButtonGroup](./references/ButtonGroup.md)
133
134
  - [Banner](./references/Banner.md)
134
135
  - [Slider](./references/Slider.md)
136
+ - [ThemeModeSwitch](./references/ThemeModeSwitch.md)
@@ -0,0 +1,79 @@
1
+ # TableHeading
2
+
3
+ A heading bar for tables and list views. It is a composable container whose main part is a search input, with optional predefined `Title` and `EndContainer` slots that can hold any content (actions, buttons, etc.).
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { TableHeading } from '@redis-ui/components';
9
+ ```
10
+
11
+ ## Props
12
+
13
+ `TableHeadingProps` is `HTMLAttributes<HTMLDivElement>` — the container forwards all standard div attributes.
14
+
15
+ | Prop | Type | Default | Description |
16
+ |------|------|---------|-------------|
17
+ | `children` | `ReactNode` | - | Heading content, typically `TableHeading.Title`, `TableHeading.SearchInput`, and `TableHeading.EndContainer`. |
18
+ | `className` | `string` | - | Applied to the container element. |
19
+ | `style` | `CSSProperties` | - | Applied to the container element. |
20
+ | `other div attributes` | `HTMLAttributes<HTMLDivElement>` | - | Any other div attributes are forwarded to the container. |
21
+
22
+ ## Sub-components
23
+
24
+ - `TableHeading.Title` - Heading text for the bar. Accepts `TypographyHeadingProps` and renders a small `Typography.Heading`.
25
+ - `TableHeading.SearchInput` - The pre-styled search field for the heading (a `SearchInput`); forwards `SearchInput` props such as `placeholder`. See the [SearchInput](./SearchInput.md) reference.
26
+ - `TableHeading.EndContainer` - Trailing slot for actions/controls; can contain any content.
27
+
28
+ ## Examples
29
+
30
+ ### Playground
31
+
32
+ ```tsx
33
+ import { TableHeading } from '@redis-ui/components';
34
+
35
+ <TableHeading>
36
+ <TableHeading.Title>Title</TableHeading.Title>
37
+ <TableHeading.SearchInput />
38
+ <TableHeading.EndContainer>EndContainer</TableHeading.EndContainer>
39
+ </TableHeading>
40
+ ```
41
+
42
+ ### Simple
43
+
44
+ > `TableHeading` can contain any components. The main part of that bar is `SearchInput`.
45
+
46
+ ```tsx
47
+ import { TableHeading } from '@redis-ui/components';
48
+
49
+ <TableHeading>
50
+ <TableHeading.SearchInput placeholder="Search table" />
51
+ </TableHeading>
52
+ ```
53
+
54
+ ### FullComposition
55
+
56
+ > In addition to `SearchInput`, the `TableHeading` also has predefined `Title` and `EndContainer`, each of which can contain any content.
57
+
58
+ ```tsx
59
+ import { ActionIconButton, Button, TableHeading, TextButton } from '@redis-ui/components';
60
+ import { ExportIcon, PlusIcon } from '@redis-ui/icons';
61
+
62
+ <TableHeading>
63
+ <TableHeading.Title>Title</TableHeading.Title>
64
+ <TableHeading.SearchInput placeholder="Search table" />
65
+ <TableHeading.EndContainer>
66
+ <TextButton>
67
+ <Button.Icon icon={ExportIcon} />
68
+ Export
69
+ </TextButton>
70
+ <ActionIconButton size="L" icon={PlusIcon} variant="primary" />
71
+ </TableHeading.EndContainer>
72
+ </TableHeading>
73
+ ```
74
+
75
+ ## Notes
76
+
77
+ - `TableHeading` is a composition container — it can hold any components, but the main element is the `SearchInput`.
78
+ - `Title` and `EndContainer` are optional predefined slots; each accepts arbitrary content.
79
+ - `TableHeading.Title` forwards `Typography.Heading` props (it renders a small heading).
@@ -0,0 +1,72 @@
1
+ # ThemeModeSwitch
2
+
3
+ A pre-built control that lets the user switch the app's theme mode. By default it is a light/dark toggle; when the surrounding provider allows the system mode it becomes a menu offering Light, Dark, and Browser options. It must be rendered inside a `SwitchableModeThemeProvider` (or an equivalent `ThemeModeSwitchingProvider` context) from `@redis-ui/styles`.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { ThemeModeSwitch } from '@redis-ui/components';
9
+ ```
10
+
11
+ The theme mode provider comes from the styles package:
12
+
13
+ ```tsx
14
+ import { SwitchableModeThemeProvider } from '@redis-ui/styles';
15
+ ```
16
+
17
+ ## Props
18
+
19
+ | Prop | Type | Default | Description |
20
+ |------|------|---------|-------------|
21
+ | `onThemeModeChange` | `(newThemeMode: UserThemeMode) => void` | - | Called after the theme mode change is applied. |
22
+ | `switchTimeoutMs` | `number` | `300` | Delay before the selected mode is applied, matching the switch transition. |
23
+ | `other element attributes` | `Omit<HTMLAttributes<HTMLElement>, 'onClick' \| 'onChange'>` | - | Remaining HTML attributes are forwarded to the control (`onClick`/`onChange` are managed internally). |
24
+
25
+ ## Hooks
26
+
27
+ - `useThemeModeSwitch` - Powers `ThemeModeSwitch`; use it to build a custom switcher. Accepts `{ onThemeModeChange?, switchTimeoutMs? }` and returns `{ allowSystemThemeMode, themeMode, setThemeMode, toggleThemeMode }`.
28
+
29
+ ## Examples
30
+
31
+ ### Basic Usage
32
+
33
+ > Provide the theme modes once at the app root, then drop the switch anywhere inside it.
34
+
35
+ ```tsx
36
+ import { SwitchableModeThemeProvider } from '@redis-ui/styles';
37
+ import { themeDark, themeLight } from './themes'; // app extended themes, based on redis-ui-styles
38
+
39
+ <SwitchableModeThemeProvider
40
+ lightTheme={themeLight}
41
+ darkTheme={themeDark}
42
+ allowSystemThemeMode={allowSystemThemeMode}
43
+ storageKey={storageKey}
44
+ >
45
+ <App />
46
+ </SwitchableModeThemeProvider>;
47
+ ```
48
+
49
+ ```tsx
50
+ import { ThemeModeSwitch } from '@redis-ui/components';
51
+
52
+ <ThemeModeSwitch onThemeModeChange={notifyUserSwitchedMode} />;
53
+ ```
54
+
55
+ ### Custom switcher with the hook
56
+
57
+ > When the built-in control doesn't fit, build your own from `useThemeModeSwitch` (or the lower-level `useThemeModeSwitchingContext` from the styles package).
58
+
59
+ ```tsx
60
+ import { useThemeModeSwitch } from '@redis-ui/components';
61
+
62
+ const { themeMode, toggleThemeMode } = useThemeModeSwitch({ onThemeModeChange });
63
+
64
+ <button onClick={toggleThemeMode}>Current mode: {themeMode}</button>;
65
+ ```
66
+
67
+ ## Notes
68
+
69
+ - `ThemeModeSwitch` requires a `SwitchableModeThemeProvider` (or `ThemeModeSwitchingProvider`) ancestor — it reads and updates the theme mode through that context.
70
+ - When the provider is configured with `allowSystemThemeMode`, the control renders a menu with Light / Dark / Browser choices instead of a two-state toggle.
71
+ - `onThemeModeChange` fires only after the mode is actually applied (after `switchTimeoutMs`).
72
+ - For fully custom UI, use the exported `useThemeModeSwitch` hook rather than restyling the component internals.