@apify/ui-library 1.145.19-featrecomputearchiveat-97661e.44 → 1.146.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apify/ui-library",
3
- "version": "1.145.19-featrecomputearchiveat-97661e.44+315973ba591",
3
+ "version": "1.146.0",
4
4
  "description": "React UI library used by apify.com",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -61,7 +61,7 @@
61
61
  "styled-components": "^6.1.19"
62
62
  },
63
63
  "devDependencies": {
64
- "@apify-packages/types": "^3.369.1-featrecomputearchiveat-97661e.44+315973ba591",
64
+ "@apify-packages/types": "^3.370.0",
65
65
  "@storybook/react-vite": "^10.3.5",
66
66
  "@testing-library/react": "^16.3.2",
67
67
  "@types/hast": "^3.0.4",
@@ -78,5 +78,5 @@
78
78
  "src",
79
79
  "style"
80
80
  ],
81
- "gitHead": "315973ba591de039a3e5c9851f232e8c724d2e95"
81
+ "gitHead": "3c890632bca16ac4291d273fe7ca30820df0088e"
82
82
  }
@@ -0,0 +1,222 @@
1
+ import type { Meta } from '@storybook/react-vite';
2
+ import { forwardRef, useState } from 'react';
3
+ import styled, { css } from 'styled-components';
4
+
5
+ import { CheckboxPrimitive, RadioButtonPrimitive, Text } from '../';
6
+ import { theme } from '../../design_system/theme.js';
7
+ import {
8
+ DropdownMenuBaseComponentWithCount,
9
+ ListMenuComponent,
10
+ ListMenuItemComponent,
11
+ Menu,
12
+ type MenuOption,
13
+ type MenuProps,
14
+ } from './';
15
+
16
+ export default {
17
+ title: 'UI-Library/Menu',
18
+ component: Menu,
19
+ } as Meta<typeof Menu>;
20
+
21
+ const FRUIT_OPTIONS: MenuOption[] = [
22
+ { value: 'apple', label: 'Apple' },
23
+ { value: 'banana', label: 'Banana' },
24
+ { value: 'cherry', label: 'Cherry' },
25
+ { value: 'dragonfruit', label: 'Dragonfruit' },
26
+ { value: 'elderberry', label: 'Elderberry' },
27
+ ];
28
+
29
+ // ── Shared layout ────────────────────────────────────────────────────────────
30
+
31
+ const FiltersRow = styled.div`
32
+ padding: 2rem;
33
+ display: flex;
34
+ align-items: center;
35
+ gap: ${theme.space.space8};
36
+ flex-wrap: wrap;
37
+ `;
38
+
39
+ const OptionRow = styled.div`
40
+ display: flex;
41
+ align-items: center;
42
+ gap: ${theme.space.space8};
43
+ width: 100%;
44
+ `;
45
+
46
+ // ── Styled components matching SelectableStoreOptionsFilter ──────────────────
47
+
48
+ const FilterMenuBase = styled(DropdownMenuBaseComponentWithCount)<{ $isSelected: boolean }>`
49
+ ${({ $isSelected }) =>
50
+ $isSelected &&
51
+ css`
52
+ color: ${theme.color.primary.text};
53
+
54
+ &:before {
55
+ content: '';
56
+ display: block;
57
+ height: 6px;
58
+ width: 6px;
59
+ border-radius: 3px;
60
+ background: ${theme.color.primary.text};
61
+ }
62
+ `}
63
+ `;
64
+
65
+ const FilterMenuItem = styled(ListMenuItemComponent)`
66
+ padding: ${theme.space.space8};
67
+ display: inline-flex;
68
+ `;
69
+
70
+ const renderTextOption = ({ label }: MenuOption, _isSelected: boolean) => (
71
+ <Text size="small" as="span">
72
+ {label}
73
+ </Text>
74
+ );
75
+
76
+ // Wraps Menu with the same base/item styling used in store search filters
77
+ const StoreStyleFilter = ({
78
+ options,
79
+ value,
80
+ onSelect,
81
+ onClear,
82
+ renderOption = renderTextOption,
83
+ closeOnSelect = true,
84
+ defaultLabel,
85
+ }: MenuProps & { onClear?: () => void; defaultLabel: string }) => {
86
+ const isActive = Array.isArray(value) ? value.some((v) => v !== options[0].value) : value !== options[0].value;
87
+ const selectedCount = Array.isArray(value) ? value.filter((v) => v !== options[0].value).length : undefined;
88
+
89
+ return (
90
+ <Menu
91
+ value={value}
92
+ options={options}
93
+ onSelect={onSelect}
94
+ renderOption={renderOption}
95
+ defaultLabel={defaultLabel}
96
+ closeOnSelect={closeOnSelect}
97
+ components={{
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ MenuBase: forwardRef<HTMLElement, any>(({ children, ...props }, ref) => (
100
+ <FilterMenuBase
101
+ $isSelected={isActive}
102
+ onClear={isActive ? onClear : undefined}
103
+ selectedCount={selectedCount}
104
+ {...props}
105
+ ref={ref}
106
+ >
107
+ <Text size="small" weight="bold" as="div">
108
+ {children}
109
+ </Text>
110
+ </FilterMenuBase>
111
+ )),
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ Menu: forwardRef<HTMLElement, any>((props, ref) => <ListMenuComponent ref={ref} {...props} />),
114
+ MenuItem: FilterMenuItem,
115
+ }}
116
+ />
117
+ );
118
+ };
119
+
120
+ // ── Stories ──────────────────────────────────────────────────────────────────
121
+
122
+ export const Default = () => {
123
+ const [value, setValue] = useState(FRUIT_OPTIONS[0].value);
124
+ return (
125
+ <FiltersRow>
126
+ <StoreStyleFilter
127
+ options={FRUIT_OPTIONS}
128
+ value={value}
129
+ onSelect={(v) => setValue(v)}
130
+ defaultLabel="Fruit"
131
+ />
132
+ </FiltersRow>
133
+ );
134
+ };
135
+
136
+ export const WithCheckbox = () => {
137
+ const [value, setValue] = useState<string[]>([]);
138
+ const handleSelect = (v: string) =>
139
+ setValue((prev) => (prev.includes(v) ? prev.filter((x) => x !== v) : [...prev, v]));
140
+ return (
141
+ <FiltersRow>
142
+ <StoreStyleFilter
143
+ options={FRUIT_OPTIONS}
144
+ value={value}
145
+ onSelect={handleSelect}
146
+ closeOnSelect={false}
147
+ defaultLabel="Fruit"
148
+ renderOption={(option, isSelected) => (
149
+ <OptionRow>
150
+ {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
151
+ <CheckboxPrimitive
152
+ value={isSelected}
153
+ setValue={() => {}}
154
+ style={{ pointerEvents: 'none' } as any}
155
+ />
156
+ <Text size="small" as="span">
157
+ {option.label}
158
+ </Text>
159
+ </OptionRow>
160
+ )}
161
+ />
162
+ </FiltersRow>
163
+ );
164
+ };
165
+
166
+ export const WithRadioAndText = () => {
167
+ const [value, setValue] = useState(FRUIT_OPTIONS[0].value);
168
+ return (
169
+ <FiltersRow>
170
+ <StoreStyleFilter
171
+ options={FRUIT_OPTIONS}
172
+ value={value}
173
+ onSelect={(v) => setValue(v)}
174
+ defaultLabel="Fruit"
175
+ renderOption={(option, isSelected) => (
176
+ <OptionRow>
177
+ {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
178
+ <RadioButtonPrimitive
179
+ value={isSelected}
180
+ setValue={() => {}}
181
+ style={{ pointerEvents: 'none' } as any}
182
+ />
183
+ <Text size="small" as="span">
184
+ {option.label}
185
+ </Text>
186
+ </OptionRow>
187
+ )}
188
+ />
189
+ </FiltersRow>
190
+ );
191
+ };
192
+
193
+ export const MultiselectWithClear = () => {
194
+ const [value, setValue] = useState<string[]>([]);
195
+ const handleSelect = (v: string) =>
196
+ setValue((prev) => (prev.includes(v) ? prev.filter((x) => x !== v) : [...prev, v]));
197
+ return (
198
+ <FiltersRow>
199
+ <StoreStyleFilter
200
+ options={FRUIT_OPTIONS}
201
+ value={value}
202
+ onSelect={handleSelect}
203
+ onClear={() => setValue([])}
204
+ closeOnSelect={false}
205
+ defaultLabel="Fruit"
206
+ renderOption={(option, isSelected) => (
207
+ <OptionRow>
208
+ {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
209
+ <CheckboxPrimitive
210
+ value={isSelected}
211
+ setValue={() => {}}
212
+ style={{ pointerEvents: 'none' } as any}
213
+ />
214
+ <Text size="small" as="span">
215
+ {option.label}
216
+ </Text>
217
+ </OptionRow>
218
+ )}
219
+ />
220
+ </FiltersRow>
221
+ );
222
+ };