@redis-ui/components 43.2.1 → 44.0.2
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 +53 -429
- package/dist/Badge/Badge.types.cjs +1 -1
- package/dist/Badge/Badge.types.d.ts +1 -1
- package/dist/Badge/Badge.types.js +1 -1
- package/dist/Badge/BadgeIcon.cjs +1 -0
- package/dist/Badge/BadgeIcon.js +2 -1
- package/dist/BoxSelectionGroup/components/Item/components/Compose/Compose.style.cjs +1 -1
- package/dist/BoxSelectionGroup/components/Item/components/Compose/Compose.style.js +1 -1
- package/dist/Button/Button.style.cjs +4 -0
- package/dist/Button/Button.style.js +4 -0
- package/dist/ButtonGroup/components/Button/Button.style.cjs +1 -1
- package/dist/ButtonGroup/components/Button/Button.style.js +1 -1
- package/dist/Drawer/Drawer.cjs +12 -2
- package/dist/Drawer/Drawer.js +12 -2
- package/dist/Drawer/components/Content/Content.style.cjs +1 -1
- package/dist/Drawer/components/Content/Content.style.js +1 -1
- package/dist/Drawer/components/Overlay/Overlay.style.cjs +1 -1
- package/dist/Drawer/components/Overlay/Overlay.style.js +1 -1
- package/dist/Drawer/hooks/useReducedMotionLifecycleSync.cjs +41 -0
- package/dist/Drawer/hooks/useReducedMotionLifecycleSync.d.ts +15 -0
- package/dist/Drawer/hooks/useReducedMotionLifecycleSync.js +41 -0
- package/dist/HighlightedIcon/HighlightedIcon.cjs +2 -2
- package/dist/HighlightedIcon/HighlightedIcon.d.ts +1 -1
- package/dist/HighlightedIcon/HighlightedIcon.js +2 -2
- package/dist/HighlightedIcon/HighlightedIcon.style.cjs +6 -6
- package/dist/HighlightedIcon/HighlightedIcon.style.d.ts +2 -2
- package/dist/HighlightedIcon/HighlightedIcon.style.js +6 -6
- package/dist/HighlightedIcon/HighlightedIcon.types.d.ts +2 -2
- package/dist/Inputs/components/Compose/Compose.style.cjs +1 -1
- package/dist/Inputs/components/Compose/Compose.style.js +1 -1
- package/dist/Loader/Loader.cjs +2 -2
- package/dist/Loader/Loader.js +2 -2
- package/dist/Loader/Loader.types.d.ts +2 -1
- package/dist/Loader/index.d.ts +1 -0
- package/dist/Menu/components/Trigger/components/Arrow/Arrow.style.cjs +1 -1
- package/dist/Menu/components/Trigger/components/Arrow/Arrow.style.js +1 -1
- package/dist/Popover/components/Content/components/WindowBar/WindowBar.types.d.ts +3 -2
- package/dist/Popover/components/Content/components/WindowBar/components/Compose/Compose.style.d.ts +2 -3
- package/dist/Popover/components/Content/components/WindowBar/components/Compose/Compose.types.d.ts +2 -2
- package/dist/ProgressBar/ProgressBar.style.cjs +1 -1
- package/dist/ProgressBar/ProgressBar.style.js +1 -1
- package/dist/SearchBar/components/FiltersSection/FiltersSection.style.cjs +1 -1
- package/dist/SearchBar/components/FiltersSection/FiltersSection.style.js +1 -1
- package/dist/SearchBar/components/SearchSection/components/Actions/components/ColumnSelector/ColumnSelector.cjs +2 -0
- package/dist/SearchBar/components/SearchSection/components/Actions/components/ColumnSelector/ColumnSelector.js +2 -0
- package/dist/Section/components/Header/components/CollapseButton/CollapseButton.style.cjs +1 -1
- package/dist/Section/components/Header/components/CollapseButton/CollapseButton.style.js +1 -1
- package/dist/Select/components/Trigger/components/Arrow/Arrow.style.cjs +1 -1
- package/dist/Select/components/Trigger/components/Arrow/Arrow.style.js +1 -1
- package/dist/Select/components/Trigger/components/Compose/Compose.style.cjs +1 -1
- package/dist/Select/components/Trigger/components/Compose/Compose.style.js +1 -1
- package/dist/SideBar/SideBar.cjs +3 -1
- package/dist/SideBar/SideBar.js +3 -1
- package/dist/SideBar/SideBar.style.cjs +1 -1
- package/dist/SideBar/SideBar.style.js +1 -1
- package/dist/SideBar/components/Button/Button.style.cjs +3 -3
- package/dist/SideBar/components/Button/Button.style.js +3 -3
- package/dist/SideBar/components/Divider/Divider.style.cjs +1 -1
- package/dist/SideBar/components/Divider/Divider.style.js +1 -1
- package/dist/SideBar/components/Header/Header.style.cjs +4 -0
- package/dist/SideBar/components/Header/Header.style.js +4 -0
- package/dist/SideBar/components/Item/Item.style.cjs +1 -1
- package/dist/SideBar/components/Item/Item.style.js +1 -1
- package/dist/SideBar/components/Item/components/Text/Text.style.cjs +1 -1
- package/dist/SideBar/components/Item/components/Text/Text.style.js +1 -1
- package/dist/SideBar/components/SideBarLogo/SideBarLogo.style.cjs +1 -1
- package/dist/SideBar/components/SideBarLogo/SideBarLogo.style.js +1 -1
- package/dist/Stepper/components/Step/components/Icon/Icon.style.cjs +1 -1
- package/dist/Stepper/components/Step/components/Icon/Icon.style.js +1 -1
- package/dist/Switch/components/Switcher/Switcher.style.cjs +3 -0
- package/dist/Switch/components/Switcher/Switcher.style.js +3 -0
- package/dist/ThemeModeSwitch/ThemeModeSwitch.style.cjs +1 -1
- package/dist/ThemeModeSwitch/ThemeModeSwitch.style.js +1 -1
- package/dist/Toast/Toaster.cjs +30 -4
- package/dist/Toast/Toaster.js +30 -4
- package/dist/Toast/Toaster.style.cjs +1 -1
- package/dist/Toast/Toaster.style.js +1 -1
- package/dist/Toast/components/ReducedMotionTransition/ReducedMotionTransition.cjs +22 -0
- package/dist/Toast/components/ReducedMotionTransition/ReducedMotionTransition.d.ts +3 -0
- package/dist/Toast/components/ReducedMotionTransition/ReducedMotionTransition.js +22 -0
- package/dist/Toast/core/mapping.helpers.cjs +9 -0
- package/dist/Toast/core/mapping.helpers.js +9 -0
- package/package.json +3 -3
- package/skills/redis-ui-components/references/Loader.md +14 -8
- package/skills/redis-ui-components/references/Popover.md +1 -1
- package/skills/redis-ui-components/references/Tooltip.md +1 -1
package/README.md
CHANGED
|
@@ -1,455 +1,79 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @redis-ui/components
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Redis UI component library — a themed, accessible React component library built on [styled-components](https://styled-components.com/) and [Radix UI](https://www.radix-ui.com/).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- [Component Structure](#component-structure)
|
|
9
|
-
- [File Organization](#file-organization)
|
|
10
|
-
- [Component Composition](#component-composition)
|
|
11
|
-
- [Testing](#testing)
|
|
7
|
+
Install the components package and its required peer dependencies:
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
This component library is built on top of [Radix UI](https://www.radix-ui.com/), a low-level UI component library that provides unstyled, accessible primitives for building high-quality design systems and web applications.
|
|
16
|
-
|
|
17
|
-
> **Note:** Not all components need Radix. Simple presentational components (like Button, Badge, Card) can be built from scratch using styled-components.
|
|
18
|
-
|
|
19
|
-
> **Note:** When using Radix, or any other external library for that matter, make sure you don't expose Radix types in public API. Always wrap them in our own types.
|
|
20
|
-
|
|
21
|
-
## Component Structure
|
|
22
|
-
|
|
23
|
-
### Basic Component Anatomy
|
|
24
|
-
|
|
25
|
-
Every component should follow this folder structure:
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
src/ComponentName/
|
|
29
|
-
├── ComponentName.tsx # Main component implementation
|
|
30
|
-
├── ComponentName.types.ts # Types and interfaces
|
|
31
|
-
├── ComponentName.style.ts # Styles
|
|
32
|
-
├── ComponentName.test.tsx # Unit tests
|
|
33
|
-
├── index.ts # Public exports
|
|
34
|
-
└── components/ # Sub-components (if needed)
|
|
35
|
-
└── SubComponent/
|
|
36
|
-
├── SubComponent.tsx
|
|
37
|
-
├── SubComponent.types.ts
|
|
38
|
-
└── SubComponent.style.ts
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
The tree is recurisve, so you can have sub-components inside sub-components, etc.
|
|
42
|
-
|
|
43
|
-
### Example: Simple Component
|
|
44
|
-
|
|
45
|
-
```tsx
|
|
46
|
-
// Card.tsx
|
|
47
|
-
import * as S from './Card.style';
|
|
48
|
-
import { CardProps } from './Card.types';
|
|
49
|
-
|
|
50
|
-
const Card = (props: CardProps) => <S.Card {...props} />;
|
|
51
|
-
|
|
52
|
-
export default Card;
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## File Organization
|
|
59
|
-
|
|
60
|
-
### 1. Component File (`ComponentName.tsx`)
|
|
61
|
-
|
|
62
|
-
**✅ DO:**
|
|
63
|
-
- Use `React.forwardRef` when the component should allow access to the DOM element underneath
|
|
64
|
-
- Use `forwardRefWithGenerics`, if component has generics params (also works as standard `forwardRef`)
|
|
65
|
-
- Provide default props for optional parameters
|
|
66
|
-
- Import styled components as `* as S` for namespacing
|
|
67
|
-
- Keep the main component focused and delegate to sub-components
|
|
68
|
-
- Extract complex logic into separate hooks, separate different logic to separate hooks
|
|
69
|
-
|
|
70
|
-
**❌ DON'T:**
|
|
71
|
-
- Mix business logic with presentation
|
|
72
|
-
- Hardcode theme values
|
|
73
|
-
|
|
74
|
-
### 2. Types File (`ComponentName.types.ts`)
|
|
75
|
-
|
|
76
|
-
**Conventions:**
|
|
77
|
-
- Always use separate file for types
|
|
78
|
-
- Extend HTML element attributes when wrapping native elements
|
|
79
|
-
- Use generics for flexible, reusable components
|
|
80
|
-
- If there are sub-components, create separate types file for each of them and compose the styles at the root level
|
|
81
|
-
- Use fully qualified names for exported types
|
|
82
|
-
- It is preferable to split component props into `Own` and `Rest` props,
|
|
83
|
-
which can simplify property composition and their use in special cases.
|
|
84
|
-
- Use `Own*Props` primarily to define actively processed props and props for direct inheritance
|
|
85
|
-
- Use `Rest*Props` for other props that you plan to pass as-is to inner components (e.g. HTMLAttributes<>)
|
|
86
|
-
|
|
87
|
-
```tsx
|
|
88
|
-
// Button.types.ts
|
|
89
|
-
import { ButtonHTMLAttributes } from 'react';
|
|
90
|
-
|
|
91
|
-
export const buttonSizes = ['large', 'medium', 'small'] as const;
|
|
92
|
-
export const buttonVariants = ['primary', 'destructive', 'secondary-fill'] as const;
|
|
93
|
-
|
|
94
|
-
export type ButtonSizes = (typeof buttonSizes)[number];
|
|
95
|
-
export type ButtonVariants = (typeof buttonVariants)[number];
|
|
96
|
-
|
|
97
|
-
export type OwnButtonProps = {
|
|
98
|
-
size?: ButtonSizes;
|
|
99
|
-
variant?: ButtonVariants;
|
|
100
|
-
disabled?: boolean;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export type RestButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;
|
|
104
|
-
|
|
105
|
-
export type ButtonProps = OwnButtonProps & RestButtonProps;
|
|
9
|
+
```bash
|
|
10
|
+
npm install @redis-ui/components @redis-ui/styles @redis-ui/icons styled-components
|
|
106
11
|
```
|
|
107
12
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
**✅ DO:**
|
|
111
|
-
- Access theme values via `useTheme().components.componentName`
|
|
112
|
-
- Use token maps for consistent styling (`styleFromTokens`)
|
|
113
|
-
- Use fully qualified names for exported styled components (simplifies component debugging with React DevTools)
|
|
114
|
-
|
|
115
|
-
```tsx
|
|
116
|
-
// Button.style.ts
|
|
117
|
-
import styled, { css } from 'styled-components/macro';
|
|
118
|
-
import { getFocusStyle, useTheme } from '@redislabsdev/redis-ui-styles';
|
|
119
|
-
import { ButtonProps } from './Button.types';
|
|
120
|
-
|
|
121
|
-
export const baseButtonStyle = css`
|
|
122
|
-
border: 0;
|
|
123
|
-
display: inline-flex;
|
|
124
|
-
justify-content: center;
|
|
125
|
-
align-items: center;
|
|
126
|
-
cursor: pointer;
|
|
13
|
+
## Setup
|
|
127
14
|
|
|
128
|
-
|
|
15
|
+
### 1. Import global styles
|
|
129
16
|
|
|
130
|
-
|
|
131
|
-
cursor: not-allowed;
|
|
132
|
-
}
|
|
133
|
-
`;
|
|
17
|
+
Import the normalized styles and fonts from `@redis-ui/styles` in your app entry point:
|
|
134
18
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
padding: ${({ size }) => useTheme().components.button.sizes[size].padding};
|
|
139
|
-
background-color: ${({ variant }) => useTheme().components.button.variants[variant].bgColor};
|
|
140
|
-
`;
|
|
19
|
+
```ts
|
|
20
|
+
import '@redis-ui/styles/normalized-styles.css';
|
|
21
|
+
import '@redis-ui/styles/fonts.css';
|
|
141
22
|
```
|
|
142
23
|
|
|
143
|
-
|
|
144
|
-
- Hardcode colors, spacing, or other design tokens
|
|
145
|
-
- Use inline styles
|
|
24
|
+
### 2. Provide the theme and render global styles
|
|
146
25
|
|
|
147
|
-
|
|
26
|
+
Components read all design tokens (colors, spacing, typography, etc.) from a styled-components `ThemeProvider`. You **must** wrap your application in one — without it, components will not render correctly.
|
|
148
27
|
|
|
149
|
-
|
|
150
|
-
- Export the default component
|
|
151
|
-
- Export all types using `export *`
|
|
152
|
-
- Export utility hooks/functions if applicable
|
|
153
|
-
- **Do not** export styled components
|
|
154
|
-
|
|
155
|
-
## Component Composition
|
|
156
|
-
|
|
157
|
-
> **Note:** It’s best to discuss whether composition is appropriate early on, ideally during the design phase. By the time a PR is under review, changing this decision will involve substantial changes. Feel free to reach out to FE Infra team for advice.
|
|
158
|
-
|
|
159
|
-
**Benefits:**
|
|
160
|
-
- Allows both simple and advanced usage patterns.
|
|
161
|
-
- Note that composed components should also provide simple props for non-advanced usage.
|
|
162
|
-
- Provides flexibility for custom layouts
|
|
163
|
-
- Maintains clean API surface
|
|
164
|
-
- Supports core Design System and allows custom application styling
|
|
165
|
-
|
|
166
|
-
### Object.assign Pattern
|
|
167
|
-
|
|
168
|
-
For components with sub-components and composition hierarchy, use `Object.assign`:
|
|
28
|
+
You also need to render the `CommonStyles` global style component inside the theme provider. It applies theme-aware base styles to the body, scrollbars, and reduced-motion preferences.
|
|
169
29
|
|
|
170
30
|
```tsx
|
|
171
|
-
|
|
172
|
-
import {
|
|
173
|
-
import ContentPane from './components/ContentPane/ContentPane';
|
|
174
|
-
import TabBar from './components/TabBar/TabBar';
|
|
175
|
-
import Compose from './components/Compose/Compose';
|
|
176
|
-
|
|
177
|
-
const Tabs = Object.assign(
|
|
178
|
-
({ tabs, variant, ...restProps }: TabsProps) => (
|
|
179
|
-
<Tabs.Compose {...restProps}>
|
|
180
|
-
<Tabs.TabBar tabs={tabs} variant={variant} />
|
|
181
|
-
<Tabs.ContentPane tabs={tabs} />
|
|
182
|
-
</Tabs.Compose>
|
|
183
|
-
),
|
|
184
|
-
{ Compose, TabBar, ContentPane }
|
|
185
|
-
);
|
|
31
|
+
import { ThemeProvider } from 'styled-components';
|
|
32
|
+
import { themesRebrand, CommonStyles } from '@redis-ui/styles';
|
|
186
33
|
|
|
187
|
-
|
|
34
|
+
function App() {
|
|
35
|
+
return (
|
|
36
|
+
<ThemeProvider theme={themesRebrand.light}>
|
|
37
|
+
<CommonStyles />
|
|
38
|
+
{/* your app */}
|
|
39
|
+
</ThemeProvider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
188
42
|
```
|
|
189
43
|
|
|
190
|
-
|
|
191
|
-
- The `assign()` function works better with TypeScript and code completion than direct assignment.
|
|
192
|
-
|
|
193
|
-
### Context Pattern
|
|
44
|
+
#### Dark mode support
|
|
194
45
|
|
|
195
|
-
For
|
|
46
|
+
For automatic light/dark mode switching with persistence, use the `SwitchableModeThemeProvider`:
|
|
196
47
|
|
|
197
|
-
Create component contexts in separate file: `ComponentName.context.ts(x)`
|
|
198
|
-
|
|
199
|
-
For most cases you can use standard component context generator with first two returned values: context hook and provider
|
|
200
48
|
```tsx
|
|
201
|
-
import {
|
|
49
|
+
import { SwitchableModeThemeProvider, themesRebrand, CommonStyles } from '@redis-ui/styles';
|
|
202
50
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
51
|
+
function App() {
|
|
52
|
+
return (
|
|
53
|
+
<SwitchableModeThemeProvider
|
|
54
|
+
lightTheme={themesRebrand.light}
|
|
55
|
+
darkTheme={themesRebrand.dark}
|
|
56
|
+
storageKey="redis-ui-theme-mode"
|
|
57
|
+
>
|
|
58
|
+
<CommonStyles />
|
|
59
|
+
{/* your app */}
|
|
60
|
+
</SwitchableModeThemeProvider>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
207
63
|
```
|
|
208
64
|
|
|
209
|
-
|
|
210
|
-
- Context hook, returning the state
|
|
211
|
-
- Context Provider, holding the state
|
|
212
|
-
- TransProvider, which acts as the Provider if there is no ascending Provider, or becomes transparent if there is one.
|
|
65
|
+
### 3. Use components
|
|
213
66
|
|
|
214
67
|
```tsx
|
|
215
|
-
import {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
export const ComponentContext = createContext<ComponentContextParams>({
|
|
229
|
-
variant: 'default',
|
|
230
|
-
size: 'medium'
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
export const useComponentParams = (
|
|
234
|
-
customParams: Partial<ComponentContextParams> = {}
|
|
235
|
-
): ComponentContextParams => {
|
|
236
|
-
const context = useContext(ComponentContext);
|
|
237
|
-
return customParams ? assignWith({}, context, customParams, skipUndefined) : context;
|
|
238
|
-
};
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
**✅ DO:**
|
|
242
|
-
- Use context for deeply nested component communication
|
|
243
|
-
- Memoize context values to prevent unnecessary re-renders
|
|
244
|
-
- Provide sensible defaults
|
|
245
|
-
|
|
246
|
-
**❌ DON'T:**
|
|
247
|
-
- Overuse context for simple prop drilling
|
|
248
|
-
- Forget to memoize context values
|
|
249
|
-
|
|
250
|
-
### `Compose` pattern
|
|
251
|
-
|
|
252
|
-
Start component life from pure composition tree.
|
|
253
|
-
|
|
254
|
-
```tsx
|
|
255
|
-
<Tabs.Compose value={value} onChange={onChange}>
|
|
256
|
-
<Tabs.TabBar.Compose>
|
|
257
|
-
{tabs.map(({ value, label, ...rest }) => (
|
|
258
|
-
<Tabs.TabBar.Tab key={value} value={value} {...rest}>
|
|
259
|
-
{label}
|
|
260
|
-
</Tabs.TabBar.Tab>
|
|
261
|
-
))}
|
|
262
|
-
<Tabs.TabBar.Marker/>
|
|
263
|
-
</Tabs.TabBar.Compose>
|
|
264
|
-
<Tabs.ContentPane.Compose>
|
|
265
|
-
{tabs.map(({ value, content }) => (
|
|
266
|
-
<Tabs.ContentPane.Content key={value} value={value}>
|
|
267
|
-
{content}
|
|
268
|
-
</Tabs.ContentPane.Content>
|
|
269
|
-
))}
|
|
270
|
-
</Tabs.ContentPane.Compose>
|
|
271
|
-
</Tabs.Compose>
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
Each non-leaf node in the tree should be `Compose` node.
|
|
275
|
-
|
|
276
|
-
`Compose` component receives configuration props, defines states, contexts and core logic and optionally adds container, styled with DS.
|
|
277
|
-
|
|
278
|
-
Leaf components remain pure renderers, reading context and their own props.
|
|
279
|
-
|
|
280
|
-
Place components in directory tree, where each node component has its own directory and all sub-components are placed in a `components` directory within the component directory
|
|
281
|
-
|
|
282
|
-
When composition is ready, wrap each Compose component and sub-components of the level with node component, which exposes simple configuration API
|
|
283
|
-
|
|
284
|
-
`Compose` and nested components should be assigned to the node component (and used in composition in it).
|
|
285
|
-
|
|
286
|
-
```tsx
|
|
287
|
-
const Tabs = Object.assign(
|
|
288
|
-
({ tabs, ...restProps }: TabsProps) => (
|
|
289
|
-
<Tabs.Compose {...restProps}>
|
|
290
|
-
<Tabs.TabBar tabs={tabs} />
|
|
291
|
-
<Tabs.ContentPane tabs={tabs} />
|
|
292
|
-
</Tabs.Compose>
|
|
293
|
-
),
|
|
294
|
-
{ Compose, TabBar, ContentPane }
|
|
295
|
-
);
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
**✅ DO:**
|
|
299
|
-
- Define context state and logic (policy/behavior/rules) only in `Compose`
|
|
300
|
-
- Keep nested and especially leaf components focused
|
|
301
|
-
- Keep node components simple (they should only contain simple composition using `Compose` and nested components)
|
|
302
|
-
- When using Radix library, use their node components in `Compose` and their leaf components in leaf
|
|
303
|
-
- Extract and separate complex logic into separate hooks
|
|
304
|
-
|
|
305
|
-
**❌ DON'T:**
|
|
306
|
-
- Add logic, contexts, states to node components
|
|
307
|
-
- Make leaf components overcomplicated
|
|
308
|
-
- Overload composition hierarchy
|
|
309
|
-
|
|
310
|
-
**Benefits:**
|
|
311
|
-
- Configuration logic doesn’t leak
|
|
312
|
-
- Nested components stay focused (“lego bricks”)
|
|
313
|
-
- Flexibility
|
|
314
|
-
- Each NodeComponent works as configuration entry point
|
|
315
|
-
- Each node can be extracted/rearranged at any level safely
|
|
316
|
-
- Simple by default, powerful when needed
|
|
317
|
-
- node components limit configuration options to what’s most necessary
|
|
318
|
-
- if you need more tuning — go to composition
|
|
319
|
-
- DS-based by default, restylable via composition
|
|
320
|
-
- Consumers can build custom composition nodes that still respect invariants
|
|
321
|
-
|
|
322
|
-
### Form Fields
|
|
323
|
-
|
|
324
|
-
If you're developing another form component that can be used with the `FormField` component,
|
|
325
|
-
be sure to connect it with the `FormField` contexts (those that are applicable)
|
|
326
|
-
- SharedId
|
|
327
|
-
- FieldStatus
|
|
328
|
-
- FieldRequired
|
|
329
|
-
- FieldDisabled
|
|
330
|
-
- FieldReadonly
|
|
331
|
-
- FieldAdditionText
|
|
332
|
-
|
|
333
|
-
## Testing
|
|
334
|
-
|
|
335
|
-
### Test File Structure
|
|
336
|
-
|
|
337
|
-
```tsx
|
|
338
|
-
// Component.test.tsx
|
|
339
|
-
import { render, screen } from '@testing-library/react';
|
|
340
|
-
import Component from './Component';
|
|
341
|
-
|
|
342
|
-
describe('Component', () => {
|
|
343
|
-
describe('Basic rendering', () => {
|
|
344
|
-
it('should render with default props', () => {
|
|
345
|
-
render(<Component>Test content</Component>);
|
|
346
|
-
expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
describe('Variants', () => {
|
|
351
|
-
it('should render primary variant', () => {
|
|
352
|
-
render(<Component variant="primary">Primary</Component>);
|
|
353
|
-
expect(screen.getByText('Primary')).toBeInTheDocument();
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
describe('Composition mode', () => {
|
|
358
|
-
it('should render with composition components', () => {
|
|
359
|
-
render(
|
|
360
|
-
<Component.Compose>
|
|
361
|
-
<Component.Header>Header</Component.Header>
|
|
362
|
-
<Component.Body>Body</Component.Body>
|
|
363
|
-
</Component.Compose>
|
|
364
|
-
);
|
|
365
|
-
expect(screen.getByText('Header')).toBeInTheDocument();
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### **Testing Library Queries**
|
|
372
|
-
|
|
373
|
-
Prefer queries in this order:
|
|
374
|
-
1. `getByRole` (most accessible)
|
|
375
|
-
2. `getByLabelText`
|
|
376
|
-
3. `getByPlaceholderText`
|
|
377
|
-
4. `getByText`
|
|
378
|
-
5. `getByTestId` (last resort)
|
|
379
|
-
|
|
380
|
-
You can find more info in [Testing Library docs](https://testing-library.com/docs/queries/about/).
|
|
381
|
-
|
|
382
|
-
## Tips & Tricks
|
|
383
|
-
|
|
384
|
-
### 1. **Transient Props in Styled Components**
|
|
385
|
-
|
|
386
|
-
Use `$` prefix for transient props:
|
|
387
|
-
|
|
388
|
-
```tsx
|
|
389
|
-
// ✅ Good - won't pass $variant to DOM
|
|
390
|
-
const Button = styled.button<{ $variant: string }>`
|
|
391
|
-
color: ${({ $variant }) => $variant === 'primary' ? 'blue' : 'gray'};
|
|
392
|
-
`;
|
|
393
|
-
|
|
394
|
-
// ❌ Bad - will pass variant to DOM and can cause warnings
|
|
395
|
-
const Button = styled.button<{ variant: string }>`
|
|
396
|
-
color: ${({ variant }) => variant === 'primary' ? 'blue' : 'gray'};
|
|
397
|
-
`;
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
### 2. **Theme Type Safety**
|
|
401
|
-
|
|
402
|
-
Create theme types for your component in `packages/styles`:
|
|
403
|
-
|
|
404
|
-
```tsx
|
|
405
|
-
// packages/styles/src/themes/types/theme/components/myComponent.types.ts
|
|
406
|
-
export type MyComponentTheme = {
|
|
407
|
-
padding: string;
|
|
408
|
-
gap: string;
|
|
409
|
-
variants: {
|
|
410
|
-
primary: { bgColor: string; textColor: string };
|
|
411
|
-
secondary: { bgColor: string; textColor: string };
|
|
412
|
-
};
|
|
413
|
-
};
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
Then add to all theme files (light, dark, light2, dark2).
|
|
417
|
-
|
|
418
|
-
### 3. **Composition Helpers**
|
|
419
|
-
|
|
420
|
-
Use helper types from `Helpers`:
|
|
421
|
-
|
|
422
|
-
```tsx
|
|
423
|
-
import { ComposeElementProps, ChildFree } from '../Helpers';
|
|
424
|
-
|
|
425
|
-
// ComposeElementProps: Standard props for composition components
|
|
426
|
-
// ChildFree: Omits children from props
|
|
427
|
-
type MyProps = ChildFree<ComposeElementProps>;
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
### 4. **Storybook Integration**
|
|
431
|
-
|
|
432
|
-
Create stories in `docs/stories/ComponentName/`:
|
|
433
|
-
|
|
434
|
-
```tsx
|
|
435
|
-
// ComponentName.stories.tsx
|
|
436
|
-
import { Meta, StoryObj } from '@storybook/react';
|
|
437
|
-
import { ComponentName } from '@redislabsdev/redis-ui-components';
|
|
438
|
-
|
|
439
|
-
const meta: Meta<typeof ComponentName> = {
|
|
440
|
-
component: ComponentName,
|
|
441
|
-
title: 'Components/ComponentName'
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
export default meta;
|
|
445
|
-
type Story = StoryObj<typeof ComponentName>;
|
|
446
|
-
|
|
447
|
-
export const Playground: Story = {
|
|
448
|
-
args: {
|
|
449
|
-
variant: 'primary',
|
|
450
|
-
size: 'medium'
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
> **Note:** For composition components, create a separate `ComponentNameCompose.mdx` file.
|
|
68
|
+
import { Button } from '@redis-ui/components';
|
|
69
|
+
|
|
70
|
+
function MyPage() {
|
|
71
|
+
return (
|
|
72
|
+
<div>
|
|
73
|
+
<Button variant="primary" size="medium">
|
|
74
|
+
Click me
|
|
75
|
+
</Button>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const badgeVariants = ["white", "light", "default", "informative", "success", "notice", "danger", "attention", "disabled"];
|
|
3
|
+
const badgeVariants = ["white", "light", "default", "informative", "success", "notice", "danger", "attention", "upsell", "disabled"];
|
|
4
4
|
exports.badgeVariants = badgeVariants;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IconType } from '@redislabsdev/redis-ui-icons';
|
|
2
2
|
import { Theme } from '@redislabsdev/redis-ui-styles';
|
|
3
|
-
export declare const badgeVariants: readonly ["white", "light", "default", "informative", "success", "notice", "danger", "attention", "disabled"];
|
|
3
|
+
export declare const badgeVariants: readonly ["white", "light", "default", "informative", "success", "notice", "danger", "attention", "upsell", "disabled"];
|
|
4
4
|
export type BadgeVariants = (typeof badgeVariants)[number];
|
|
5
5
|
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
6
|
label: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const badgeVariants = ["white", "light", "default", "informative", "success", "notice", "danger", "attention", "disabled"];
|
|
1
|
+
const badgeVariants = ["white", "light", "default", "informative", "success", "notice", "danger", "attention", "upsell", "disabled"];
|
|
2
2
|
export {
|
|
3
3
|
badgeVariants
|
|
4
4
|
};
|
package/dist/Badge/BadgeIcon.cjs
CHANGED
|
@@ -11,6 +11,7 @@ const BADGE_ICON_RECORD = {
|
|
|
11
11
|
notice: redisUiIcons.ToastNotificationIcon,
|
|
12
12
|
danger: redisUiIcons.ToastDangerIcon,
|
|
13
13
|
attention: redisUiIcons.ToastPendingIcon,
|
|
14
|
+
upsell: redisUiIcons.CrownIcon,
|
|
14
15
|
disabled: redisUiIcons.ToastCheckIcon
|
|
15
16
|
};
|
|
16
17
|
const BadgeIcon = ({
|
package/dist/Badge/BadgeIcon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { j as jsxRuntimeExports } from "../node_modules/react/jsx-runtime.js";
|
|
2
|
-
import { ToastCheckIcon, ToastInfoIcon, ToastNotificationIcon, ToastDangerIcon, ToastPendingIcon } from "@redislabsdev/redis-ui-icons";
|
|
2
|
+
import { ToastCheckIcon, ToastInfoIcon, ToastNotificationIcon, ToastDangerIcon, ToastPendingIcon, CrownIcon } from "@redislabsdev/redis-ui-icons";
|
|
3
3
|
const BADGE_ICON_RECORD = {
|
|
4
4
|
white: ToastCheckIcon,
|
|
5
5
|
light: ToastCheckIcon,
|
|
@@ -9,6 +9,7 @@ const BADGE_ICON_RECORD = {
|
|
|
9
9
|
notice: ToastNotificationIcon,
|
|
10
10
|
danger: ToastDangerIcon,
|
|
11
11
|
attention: ToastPendingIcon,
|
|
12
|
+
upsell: CrownIcon,
|
|
12
13
|
disabled: ToastCheckIcon
|
|
13
14
|
};
|
|
14
15
|
const BadgeIcon = ({
|
|
@@ -59,7 +59,7 @@ const getStateStyle = (theme, isChecked = false) => {
|
|
|
59
59
|
const BoxSelectionGroupItemCompose = _styled__default.default(index.Root).withConfig({
|
|
60
60
|
displayName: "Composestyle__BoxSelectionGroupItemCompose",
|
|
61
61
|
componentId: "RedisUI__sc-17mo3lu-1"
|
|
62
|
-
})(["align-items:unset;justify-content:center;line-height:1.25;text-align:unset;position:relative;padding-inline:0;padding-block:0;flex:1;white-space:nowrap;cursor:", ";overflow:hidden;transition:all 0.1s;border-radius:", ";border-width:", ";border-style:", ";", ";", ";"], ({
|
|
62
|
+
})(["align-items:unset;justify-content:center;line-height:1.25;text-align:unset;position:relative;padding-inline:0;padding-block:0;flex:1;white-space:nowrap;cursor:", ";overflow:hidden;transition:all 0.1s;@media (prefers-reduced-motion:reduce){transition:none;}border-radius:", ";border-width:", ";border-style:", ";", ";", ";"], ({
|
|
63
63
|
disabled
|
|
64
64
|
}) => !disabled ? "pointer" : "not-allowed", () => redisUiStyles.useTheme().components.boxSelectionGroup.item.borderRadius, () => redisUiStyles.useTheme().components.boxSelectionGroup.item.borderWidth, () => redisUiStyles.useTheme().components.boxSelectionGroup.item.borderStyle, ({
|
|
65
65
|
pressed
|
|
@@ -55,7 +55,7 @@ const getStateStyle = (theme, isChecked = false) => {
|
|
|
55
55
|
const BoxSelectionGroupItemCompose = _styled(Root).withConfig({
|
|
56
56
|
displayName: "Composestyle__BoxSelectionGroupItemCompose",
|
|
57
57
|
componentId: "RedisUI__sc-17mo3lu-1"
|
|
58
|
-
})(["align-items:unset;justify-content:center;line-height:1.25;text-align:unset;position:relative;padding-inline:0;padding-block:0;flex:1;white-space:nowrap;cursor:", ";overflow:hidden;transition:all 0.1s;border-radius:", ";border-width:", ";border-style:", ";", ";", ";"], ({
|
|
58
|
+
})(["align-items:unset;justify-content:center;line-height:1.25;text-align:unset;position:relative;padding-inline:0;padding-block:0;flex:1;white-space:nowrap;cursor:", ";overflow:hidden;transition:all 0.1s;@media (prefers-reduced-motion:reduce){transition:none;}border-radius:", ";border-width:", ";border-style:", ";", ";", ";"], ({
|
|
59
59
|
disabled
|
|
60
60
|
}) => !disabled ? "pointer" : "not-allowed", () => useTheme().components.boxSelectionGroup.item.borderRadius, () => useTheme().components.boxSelectionGroup.item.borderWidth, () => useTheme().components.boxSelectionGroup.item.borderStyle, ({
|
|
61
61
|
pressed
|
|
@@ -18,6 +18,10 @@ const baseButtonStyle = _styled.css`
|
|
|
18
18
|
transition: all 130ms ease-in-out, outline-offset 0ms, outline 0ms;
|
|
19
19
|
cursor: pointer;
|
|
20
20
|
|
|
21
|
+
@media (prefers-reduced-motion: reduce) {
|
|
22
|
+
transition: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
21
25
|
${redisUiStyles.getFocusStyle()};
|
|
22
26
|
|
|
23
27
|
&:disabled {
|
|
@@ -9,7 +9,7 @@ const _styled__default = /* @__PURE__ */ _interopDefault(_styled);
|
|
|
9
9
|
const Button = _styled__default.default.button.withConfig({
|
|
10
10
|
displayName: "Buttonstyle__Button",
|
|
11
11
|
componentId: "RedisUI__sc-m6kucg-0"
|
|
12
|
-
})(["cursor:pointer;border:none;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:1.25;max-width:100%;min-width:", ";text-align:center;text-decoration:none;transition:color 0.13s ease-in-out,background-color 0.13s ease-in-out,border-color 0.13s ease-in-out,box-shadow 0.13s ease-in-out;user-select:none;vertical-align:middle;white-space:nowrap;display:flex;border-radius:", ";padding:", ";justify-content:center;align-items:center;gap:", ";&:not(:last-child){border-inline-end:", ";}&:disabled{cursor:not-allowed;}", " ", ";"], () => redisUiStyles.useTheme().components.buttonGroup.button.minWidth, () => redisUiStyles.useTheme().components.buttonGroup.button.borderRadius, () => redisUiStyles.useTheme().components.buttonGroup.button.padding, () => redisUiStyles.useTheme().components.buttonGroup.button.gap, () => redisUiStyles.useTheme().components.buttonGroup.button.border, ({
|
|
12
|
+
})(["cursor:pointer;border:none;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:1.25;max-width:100%;min-width:", ";text-align:center;text-decoration:none;transition:color 0.13s ease-in-out,background-color 0.13s ease-in-out,border-color 0.13s ease-in-out,box-shadow 0.13s ease-in-out;@media (prefers-reduced-motion:reduce){transition:none;}user-select:none;vertical-align:middle;white-space:nowrap;display:flex;border-radius:", ";padding:", ";justify-content:center;align-items:center;gap:", ";&:not(:last-child){border-inline-end:", ";}&:disabled{cursor:not-allowed;}", " ", ";"], () => redisUiStyles.useTheme().components.buttonGroup.button.minWidth, () => redisUiStyles.useTheme().components.buttonGroup.button.borderRadius, () => redisUiStyles.useTheme().components.buttonGroup.button.padding, () => redisUiStyles.useTheme().components.buttonGroup.button.gap, () => redisUiStyles.useTheme().components.buttonGroup.button.border, ({
|
|
13
13
|
isSelected
|
|
14
14
|
}) => Button_style_utils.getButtonStatesStyle(redisUiStyles.useTheme().components.buttonGroup.button.toggleStates[isSelected ? "on" : "off"], [css_utils.tokenMaps.bgColor, css_utils.tokenMaps.textColor]), redisUiStyles.getFocusStyle("-2px"));
|
|
15
15
|
exports.Button = Button;
|
|
@@ -5,7 +5,7 @@ import { tokenMaps } from "../../../Helpers/css.utils.js";
|
|
|
5
5
|
const Button = _styled.button.withConfig({
|
|
6
6
|
displayName: "Buttonstyle__Button",
|
|
7
7
|
componentId: "RedisUI__sc-m6kucg-0"
|
|
8
|
-
})(["cursor:pointer;border:none;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:1.25;max-width:100%;min-width:", ";text-align:center;text-decoration:none;transition:color 0.13s ease-in-out,background-color 0.13s ease-in-out,border-color 0.13s ease-in-out,box-shadow 0.13s ease-in-out;user-select:none;vertical-align:middle;white-space:nowrap;display:flex;border-radius:", ";padding:", ";justify-content:center;align-items:center;gap:", ";&:not(:last-child){border-inline-end:", ";}&:disabled{cursor:not-allowed;}", " ", ";"], () => useTheme().components.buttonGroup.button.minWidth, () => useTheme().components.buttonGroup.button.borderRadius, () => useTheme().components.buttonGroup.button.padding, () => useTheme().components.buttonGroup.button.gap, () => useTheme().components.buttonGroup.button.border, ({
|
|
8
|
+
})(["cursor:pointer;border:none;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:1.25;max-width:100%;min-width:", ";text-align:center;text-decoration:none;transition:color 0.13s ease-in-out,background-color 0.13s ease-in-out,border-color 0.13s ease-in-out,box-shadow 0.13s ease-in-out;@media (prefers-reduced-motion:reduce){transition:none;}user-select:none;vertical-align:middle;white-space:nowrap;display:flex;border-radius:", ";padding:", ";justify-content:center;align-items:center;gap:", ";&:not(:last-child){border-inline-end:", ";}&:disabled{cursor:not-allowed;}", " ", ";"], () => useTheme().components.buttonGroup.button.minWidth, () => useTheme().components.buttonGroup.button.borderRadius, () => useTheme().components.buttonGroup.button.padding, () => useTheme().components.buttonGroup.button.gap, () => useTheme().components.buttonGroup.button.border, ({
|
|
9
9
|
isSelected
|
|
10
10
|
}) => getButtonStatesStyle(useTheme().components.buttonGroup.button.toggleStates[isSelected ? "on" : "off"], [tokenMaps.bgColor, tokenMaps.textColor]), getFocusStyle("-2px"));
|
|
11
11
|
export {
|
package/dist/Drawer/Drawer.cjs
CHANGED
|
@@ -4,6 +4,7 @@ const jsxRuntime = require("../node_modules/react/jsx-runtime.cjs");
|
|
|
4
4
|
const index = require("../node_modules/@radix-ui/react-dialog/dist/index.cjs");
|
|
5
5
|
const redisUiStyles = require("@redislabsdev/redis-ui-styles");
|
|
6
6
|
const Drawer_context = require("./Drawer.context.cjs");
|
|
7
|
+
const useReducedMotionLifecycleSync = require("./hooks/useReducedMotionLifecycleSync.cjs");
|
|
7
8
|
const Overlay = require("./components/Overlay/Overlay.cjs");
|
|
8
9
|
const Content = require("./components/Content/Content.cjs");
|
|
9
10
|
const Header = require("./components/Header/Header.cjs");
|
|
@@ -24,6 +25,15 @@ const Drawer = Object.assign(({
|
|
|
24
25
|
...props
|
|
25
26
|
}) => {
|
|
26
27
|
const theme = redisUiStyles.useTheme();
|
|
28
|
+
const {
|
|
29
|
+
prefersReducedMotion
|
|
30
|
+
} = useReducedMotionLifecycleSync.useReducedMotionLifecycleSync({
|
|
31
|
+
open,
|
|
32
|
+
onDrawerWillOpen,
|
|
33
|
+
onDrawerDidOpen,
|
|
34
|
+
onDrawerWillClose,
|
|
35
|
+
onDrawerDidClose
|
|
36
|
+
});
|
|
27
37
|
const handleOnAnimationStart = () => {
|
|
28
38
|
if (open) {
|
|
29
39
|
onDrawerWillOpen == null ? void 0 : onDrawerWillOpen();
|
|
@@ -52,8 +62,8 @@ const Drawer = Object.assign(({
|
|
|
52
62
|
children: jsxRuntime.jsxRuntimeExports.jsxs(index.Portal, {
|
|
53
63
|
container: containerElement,
|
|
54
64
|
children: [persistent && jsxRuntime.jsxRuntimeExports.jsx(Overlay.default, {}), jsxRuntime.jsxRuntimeExports.jsx(Content.default, {
|
|
55
|
-
onAnimationStart: handleOnAnimationStart,
|
|
56
|
-
onAnimationEndCapture: handleOnAnimationEnd,
|
|
65
|
+
onAnimationStart: prefersReducedMotion ? void 0 : handleOnAnimationStart,
|
|
66
|
+
onAnimationEndCapture: prefersReducedMotion ? void 0 : handleOnAnimationEnd,
|
|
57
67
|
...props,
|
|
58
68
|
children
|
|
59
69
|
})]
|