polpo 0.1.0 → 0.1.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/.storybook/theme.ts +2 -2
- package/.turbo/turbo-build.log +0 -77
- package/.turbo/turbo-lint.log +1 -1
- package/README.md +2 -5
- package/dist/chunk-CFYQBHH5.js +3 -0
- package/dist/chunk-CFYQBHH5.js.map +1 -0
- package/dist/chunk-MAWW6AA7.js +3 -0
- package/dist/chunk-MAWW6AA7.js.map +1 -0
- package/dist/get-modal-position-drle0OjP.d.cts +49 -0
- package/dist/get-modal-position-drle0OjP.d.ts +49 -0
- package/dist/helpers.cjs +1 -1
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +9 -2
- package/dist/helpers.d.ts +9 -2
- package/dist/helpers.js +1 -1
- package/dist/hooks.cjs +1 -1
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +59 -21
- package/dist/hooks.d.ts +59 -21
- package/dist/hooks.js +1 -1
- package/dist/ui.cjs +601 -389
- package/dist/ui.cjs.map +1 -1
- package/dist/ui.d.cts +97 -77
- package/dist/ui.d.ts +97 -77
- package/dist/ui.js +585 -373
- package/dist/ui.js.map +1 -1
- package/dist/use-modal-in-container-DiNW1PE_.d.cts +34 -0
- package/dist/use-modal-in-container-neGo-kMk.d.ts +34 -0
- package/package.json +5 -5
- package/src/components/buttons/button/button.stories.tsx +4 -4
- package/src/components/buttons/button/button.style.ts +10 -5
- package/src/components/buttons/button/button.tsx +7 -19
- package/src/components/cards/flip-card/flip-card.tsx +1 -1
- package/src/components/cursor/cursor.stories.tsx +35 -0
- package/src/components/cursor/cursor.style.ts +73 -0
- package/src/components/cursor/cursor.tsx +49 -0
- package/src/components/cursor/index.ts +1 -0
- package/src/components/form/checkbox/checkbox.stories.tsx +51 -0
- package/src/components/form/checkbox/checkbox.style.ts +73 -37
- package/src/components/form/checkbox/checkbox.tsx +38 -4
- package/src/components/form/field/field.stories.tsx +5 -1
- package/src/components/form/field/field.style.ts +12 -0
- package/src/components/form/field/field.tsx +3 -1
- package/src/components/form/field/field.types.ts +6 -0
- package/src/components/form/input-color/input-color.style.ts +5 -4
- package/src/components/form/input-color/input-color.tsx +41 -44
- package/src/components/form/radio/radio.stories.tsx +29 -5
- package/src/components/form/radio/radio.style.ts +45 -24
- package/src/components/form/radio/radio.tsx +22 -3
- package/src/components/form/select/options.tsx +119 -67
- package/src/components/form/select/select.stories.tsx +103 -42
- package/src/components/form/select/select.style.ts +10 -92
- package/src/components/form/select/select.tsx +19 -42
- package/src/components/form/select/select.types.ts +4 -21
- package/src/components/form/slider/slider.style.ts +2 -0
- package/src/components/icon/icons/social.tsx +17 -1
- package/src/components/index.ts +1 -0
- package/src/components/infinity-scroll/infinity-scroll.tsx +1 -1
- package/src/components/line/line.stories.tsx +3 -4
- package/src/components/modals/action-modal/action-modal.stories.tsx +58 -39
- package/src/components/modals/action-modal/action-modal.style.ts +13 -25
- package/src/components/modals/action-modal/action-modal.tsx +68 -70
- package/src/components/modals/aside-modal/aside-modal.stories.tsx +11 -15
- package/src/components/modals/aside-modal/aside-modal.style.ts +17 -37
- package/src/components/modals/aside-modal/aside-modal.tsx +41 -43
- package/src/components/modals/confirmation-modal/confirmation-modal.stories.tsx +21 -9
- package/src/components/modals/index.ts +2 -0
- package/src/components/modals/menu/index.ts +1 -0
- package/src/components/modals/menu/menu.stories.tsx +69 -0
- package/src/components/modals/menu/menu.style.ts +62 -0
- package/src/components/modals/menu/menu.tsx +142 -0
- package/src/components/modals/modal/backdrop.tsx +70 -0
- package/src/components/modals/modal/index.ts +1 -0
- package/src/components/modals/modal/modal.stories.tsx +325 -0
- package/src/components/modals/modal/modal.style.ts +62 -2
- package/src/components/modals/modal/modal.tsx +82 -123
- package/src/components/modals/portal/index.ts +1 -0
- package/src/components/modals/portal/portal.tsx +18 -0
- package/src/components/tabs/tabs-list.tsx +13 -10
- package/src/components/tabs/tabs.style.ts +48 -43
- package/src/components/tag/tag.stories.tsx +11 -12
- package/src/components/tag/tag.style.ts +9 -4
- package/src/components/tag/tag.tsx +2 -12
- package/src/components/tooltips/tooltip/tooltip.stories.tsx +5 -2
- package/src/components/tooltips/tooltip/tooltip.style.ts +37 -6
- package/src/components/tooltips/tooltip/tooltip.tsx +33 -19
- package/src/components/typography/typography.stories.tsx +3 -1
- package/src/components/typography/typography.tsx +21 -0
- package/src/contexts/theme-context/theme.animations.ts +91 -2
- package/src/contexts/theme-context/theme.defaults.ts +1 -1
- package/src/core/http-client.ts +49 -47
- package/src/core/variants/color.ts +3 -30
- package/src/core/variants/radius.ts +12 -41
- package/src/core/variants/size.ts +8 -33
- package/src/helpers/get-modal-position-relative-to-screen.ts +86 -0
- package/src/helpers/get-modal-position.ts +173 -28
- package/src/helpers/index.ts +1 -0
- package/src/hooks/index.ts +9 -3
- package/src/hooks/use-click-outside.ts +32 -0
- package/src/hooks/use-cookie.ts +124 -0
- package/src/hooks/use-dimensions.ts +11 -14
- package/src/hooks/use-dom-container.ts +32 -0
- package/src/hooks/use-event-listener.ts +4 -4
- package/src/hooks/use-geolocation.ts +63 -0
- package/src/hooks/use-in-view.ts +9 -11
- package/src/hooks/use-intersection-observer.ts +19 -0
- package/src/hooks/use-modal-in-container.ts +60 -52
- package/src/hooks/use-modal-transition.ts +54 -0
- package/src/hooks/use-modal.ts +21 -0
- package/src/hooks/use-mouse-position.ts +55 -7
- package/src/hooks/use-resize-observer.ts +18 -0
- package/src/stories/GettingStarted.mdx +2 -6
- package/svg/Name=npm, Category=social.svg +3 -0
- package/tsconfig.json +1 -0
- package/vite.config.ts +1 -0
- package/.turbo/daemon/f5c5c8fb195b01d0-turbo.log.2024-05-26 +0 -0
- package/.turbo/turbo-build$colon$watch.log +0 -96
- package/.turbo/turbo-build-storybook.log +0 -0
- package/.turbo/turbo-lint$colon$fix.log +0 -2
- package/dist/chunk-M4KRSYE7.js +0 -3
- package/dist/chunk-M4KRSYE7.js.map +0 -1
- package/dist/chunk-U5XSMSKZ.js +0 -3
- package/dist/chunk-U5XSMSKZ.js.map +0 -1
- package/dist/get-modal-position-DPftPoU2.d.cts +0 -28
- package/dist/get-modal-position-DPftPoU2.d.ts +0 -28
- package/src/components/form/select/select-option.tsx +0 -84
- package/src/hooks/use-observer.ts +0 -18
- package/src/hooks/use-on-click-outside-ref.ts +0 -17
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useTheme } from 'styled-components';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { Modal } from '../../modals';
|
|
6
|
-
import { Typography } from '../../typography';
|
|
4
|
+
import { Menu } from '../../modals';
|
|
7
5
|
|
|
8
|
-
import {
|
|
9
|
-
import { OptionsHeaderStyle, OptionsStyle } from './select.style';
|
|
6
|
+
import { OptionsHeaderStyle, OptionsMenuStyle } from './select.style';
|
|
10
7
|
import { OptionsProps, SelectItem } from './select.types';
|
|
11
8
|
|
|
12
|
-
import { useEventListener, useMediaQuery } from '@polpo/hooks';
|
|
9
|
+
import { useClassNames, useEventListener, useMediaQuery, useResizeObserver } from '@polpo/hooks';
|
|
13
10
|
|
|
14
11
|
export const Options = <T extends SelectItem>({
|
|
15
12
|
onSearchQuery,
|
|
@@ -21,17 +18,19 @@ export const Options = <T extends SelectItem>({
|
|
|
21
18
|
selectOption,
|
|
22
19
|
unselectOption,
|
|
23
20
|
isOpen,
|
|
24
|
-
style,
|
|
25
21
|
options,
|
|
26
22
|
loadMore = () => null,
|
|
27
23
|
isLoading = false,
|
|
28
24
|
hasNextPage = false,
|
|
29
|
-
|
|
25
|
+
containerRef,
|
|
30
26
|
Component,
|
|
31
|
-
|
|
27
|
+
onClose,
|
|
28
|
+
emptyMessage = 'No options to select',
|
|
29
|
+
maxHeight = 400,
|
|
32
30
|
}: OptionsProps<T>) => {
|
|
33
31
|
const theme = useTheme();
|
|
34
|
-
const
|
|
32
|
+
const modalContainerRef = useRef<HTMLElement>(null);
|
|
33
|
+
const isMobile = useMediaQuery(`(max-width: ${theme.constants.breakpoints.mobileL})`);
|
|
35
34
|
const [internalSearchQuery, setInternalSearchQuery] = useState('');
|
|
36
35
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
37
36
|
|
|
@@ -70,71 +69,124 @@ export const Options = <T extends SelectItem>({
|
|
|
70
69
|
if (searchInputRef.current) {
|
|
71
70
|
searchInputRef.current.focus();
|
|
72
71
|
} else {
|
|
73
|
-
|
|
72
|
+
modalContainerRef.current?.focus();
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
|
-
}, [isOpen,
|
|
75
|
+
}, [isOpen, modalContainerRef]);
|
|
76
|
+
|
|
77
|
+
const optionIsSelected = useCallback(
|
|
78
|
+
(option: T) => !!value && value !== '' && compareValueOrValuesAreEqual(option, value),
|
|
79
|
+
[compareValueOrValuesAreEqual, value],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const handleKeyDown = useCallback(
|
|
83
|
+
(option: T) => (e: React.KeyboardEvent) => {
|
|
84
|
+
if (['Enter', ' '].includes(e.key)) {
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
|
|
87
|
+
const selected = optionIsSelected(option);
|
|
88
|
+
|
|
89
|
+
if (selected && multiselect) {
|
|
90
|
+
unselectOption(option);
|
|
91
|
+
} else {
|
|
92
|
+
selectOption(option);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[multiselect, selectOption, optionIsSelected, unselectOption],
|
|
97
|
+
);
|
|
77
98
|
|
|
78
99
|
const renderInternalOption = useCallback(
|
|
79
|
-
(option: T, key: number) =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
(option: T, key: number) => {
|
|
101
|
+
const selected = optionIsSelected(option);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Menu.Option
|
|
105
|
+
key={key}
|
|
106
|
+
id={`${key}`}
|
|
107
|
+
label={<Component data={option} isSelected={selected} multiselect={multiselect} />}
|
|
108
|
+
onClick={(selected: boolean) => {
|
|
109
|
+
if (multiselect) {
|
|
110
|
+
if (selected) selectOption(option);
|
|
111
|
+
else unselectOption(option);
|
|
112
|
+
} else {
|
|
113
|
+
selectOption(option);
|
|
114
|
+
}
|
|
115
|
+
}}
|
|
116
|
+
onKeyDown={handleKeyDown(option)}
|
|
117
|
+
asCheckbox={multiselect}
|
|
118
|
+
selected={selected}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
[optionIsSelected, Component, multiselect, handleKeyDown, selectOption, unselectOption],
|
|
93
123
|
);
|
|
94
124
|
|
|
125
|
+
const [height, setHeight] = useState<string>('400px');
|
|
126
|
+
|
|
127
|
+
const getHeight = useCallback(() => {
|
|
128
|
+
const containerBottom = containerRef.current?.getBoundingClientRect().bottom ?? 0;
|
|
129
|
+
|
|
130
|
+
const height = Math.min(window.innerHeight - containerBottom - 20, maxHeight);
|
|
131
|
+
|
|
132
|
+
setHeight(`${Math.round(height)}px`);
|
|
133
|
+
}, [containerRef, maxHeight]);
|
|
134
|
+
|
|
135
|
+
useResizeObserver(containerRef, getHeight);
|
|
136
|
+
useEventListener('resize', getHeight);
|
|
137
|
+
|
|
138
|
+
const menuClassName = useClassNames({
|
|
139
|
+
'search-input': Boolean(onSearchQuery),
|
|
140
|
+
});
|
|
141
|
+
|
|
95
142
|
return (
|
|
96
|
-
<
|
|
97
|
-
isOpen={isOpen}
|
|
143
|
+
<OptionsMenuStyle
|
|
98
144
|
id='form-select'
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
145
|
+
isOpen={isOpen}
|
|
146
|
+
onClose={onClose}
|
|
147
|
+
backdrop={isMobile ? 'blur' : 'opaque'}
|
|
148
|
+
opacity={isMobile ? 0.8 : 0.4}
|
|
149
|
+
position={isMobile ? 'center' : 'bottom'}
|
|
150
|
+
offset={5}
|
|
151
|
+
transitionDuration={200}
|
|
152
|
+
containerRef={isMobile ? undefined : containerRef}
|
|
153
|
+
className={menuClassName}
|
|
154
|
+
style={
|
|
155
|
+
isMobile
|
|
156
|
+
? {
|
|
157
|
+
maxHeight: window.innerHeight - 100,
|
|
158
|
+
width: window.innerWidth - 100,
|
|
159
|
+
}
|
|
160
|
+
: {
|
|
161
|
+
maxHeight: height,
|
|
162
|
+
minHeight: '200px',
|
|
163
|
+
width: containerRef.current?.offsetWidth ?? 'auto',
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
rootStyle={isMobile ? {} : {}}
|
|
102
167
|
>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
) : (
|
|
127
|
-
<InfinityScroll
|
|
128
|
-
isLoading={isLoading}
|
|
129
|
-
hasNextPage={hasNextPage}
|
|
130
|
-
loadMore={loadMore}
|
|
131
|
-
data={options}
|
|
132
|
-
renderItem={renderInternalOption}
|
|
133
|
-
/>
|
|
134
|
-
)}
|
|
135
|
-
</ul>
|
|
136
|
-
</section>
|
|
137
|
-
</OptionsStyle>
|
|
138
|
-
</Modal>
|
|
168
|
+
{onSearchQuery && (
|
|
169
|
+
<OptionsHeaderStyle>
|
|
170
|
+
<input
|
|
171
|
+
name='query'
|
|
172
|
+
className='input-search'
|
|
173
|
+
value={searchQueryValue ?? internalSearchQuery}
|
|
174
|
+
onChange={handleSearchQuery}
|
|
175
|
+
placeholder={searchQueryPlaceholder}
|
|
176
|
+
onClick={e => e.stopPropagation()}
|
|
177
|
+
ref={searchInputRef}
|
|
178
|
+
autoFocus
|
|
179
|
+
/>
|
|
180
|
+
</OptionsHeaderStyle>
|
|
181
|
+
)}
|
|
182
|
+
<Menu.OptionsGroup
|
|
183
|
+
isLoading={isLoading}
|
|
184
|
+
hasNextPage={hasNextPage}
|
|
185
|
+
loadMore={loadMore}
|
|
186
|
+
data={options}
|
|
187
|
+
renderItem={renderInternalOption}
|
|
188
|
+
emptyMessage={emptyMessage}
|
|
189
|
+
/>
|
|
190
|
+
</OptionsMenuStyle>
|
|
139
191
|
);
|
|
140
192
|
};
|
|
@@ -7,7 +7,7 @@ import { Select } from './select';
|
|
|
7
7
|
|
|
8
8
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
9
9
|
|
|
10
|
-
const meta: Meta<typeof Select
|
|
10
|
+
const meta: Meta<typeof Select<string>> = {
|
|
11
11
|
title: 'Form/Select',
|
|
12
12
|
component: Select,
|
|
13
13
|
argTypes: {
|
|
@@ -25,65 +25,126 @@ const meta: Meta<typeof Select> = {
|
|
|
25
25
|
maxOptions: { control: { type: 'number' } },
|
|
26
26
|
showClearOption: { control: 'boolean' },
|
|
27
27
|
multiselect: { control: false },
|
|
28
|
-
optionVariant: {
|
|
29
|
-
control: 'select',
|
|
30
|
-
options: [undefined, 'checkbox', 'icon', 'default'],
|
|
31
|
-
if: { arg: 'multiselect' },
|
|
32
|
-
},
|
|
33
28
|
},
|
|
34
29
|
args: {
|
|
35
30
|
label: 'Select',
|
|
36
31
|
searchQueryPlaceholder: 'Search',
|
|
37
32
|
...FieldSharedArgs,
|
|
38
|
-
|
|
33
|
+
placeholder: 'Select an option',
|
|
34
|
+
options: [
|
|
35
|
+
'Leda Pinna',
|
|
36
|
+
'Antonio Sansone',
|
|
37
|
+
'Orlando Simeone',
|
|
38
|
+
'Giorgio Villa',
|
|
39
|
+
'Virgilio Paoli',
|
|
40
|
+
'Nicola Loi',
|
|
41
|
+
'Giordano Manzo',
|
|
42
|
+
'Emilio Galletti',
|
|
43
|
+
'Moira Galimberti',
|
|
44
|
+
'Fedele Spina',
|
|
45
|
+
'Débora Buzzi',
|
|
46
|
+
'Ferrari Sacco',
|
|
47
|
+
'Rosalba Lodi',
|
|
48
|
+
'Bianca Paris',
|
|
49
|
+
'Salvatrice di Paola',
|
|
50
|
+
'Antonietta Mancuso',
|
|
51
|
+
'Corradina Battistini',
|
|
52
|
+
'Elisabeth Annunziata',
|
|
53
|
+
'Federica Vinciguerra',
|
|
54
|
+
'Ennio Spinelli',
|
|
55
|
+
'Susanna Franzoni',
|
|
56
|
+
'Ottavio di Bella',
|
|
57
|
+
'Melania Genovese',
|
|
58
|
+
'Marzio Tomasi',
|
|
59
|
+
'Anselmo Bettoni',
|
|
60
|
+
'Robert Boni',
|
|
61
|
+
'Leonardo Guido',
|
|
62
|
+
'Ermelinda Biagini',
|
|
63
|
+
'Aurélio Falcone',
|
|
64
|
+
'Hubert Paolini',
|
|
65
|
+
'Imelda Sessa',
|
|
66
|
+
'Santino Viviani',
|
|
67
|
+
'Peter Franchini',
|
|
68
|
+
'Guglielmo Lorenzon',
|
|
69
|
+
'Martha Casiraghi',
|
|
70
|
+
'Stefano Cuomo',
|
|
71
|
+
'Valéria di Giovanni',
|
|
72
|
+
'Cecília Ambrosio',
|
|
73
|
+
'Vincenzina Bernardini',
|
|
74
|
+
'Renato Marchi',
|
|
75
|
+
'Raffaello Guida',
|
|
76
|
+
'Gregório Battisti',
|
|
77
|
+
'Alberto Bruni',
|
|
78
|
+
'Giovannino Lepore',
|
|
79
|
+
'Ludovica Randazzo',
|
|
80
|
+
'Iris Merli',
|
|
81
|
+
'Alida Fois',
|
|
82
|
+
'Innocenzo Palazzo',
|
|
83
|
+
'Giacinto Vergani',
|
|
84
|
+
'Alessio Drago',
|
|
85
|
+
],
|
|
86
|
+
renderOption: v => v,
|
|
39
87
|
},
|
|
40
88
|
decorators: [ContainerDecorator],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default meta;
|
|
92
|
+
type Story = StoryObj<typeof Select<string>>;
|
|
93
|
+
|
|
94
|
+
export const SingleValue: Story = {
|
|
95
|
+
args: {},
|
|
41
96
|
render: args => {
|
|
42
97
|
const [value, setValue] = useState<string | null>(null);
|
|
43
98
|
|
|
99
|
+
return <Select<string> {...args} multiselect={false} value={value} setValue={value => setValue(value)} />;
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const MultipleValues: Story = {
|
|
104
|
+
render: args => {
|
|
105
|
+
const [value, setValue] = useState<Array<string>>([]);
|
|
106
|
+
|
|
107
|
+
return <Select<string> {...args} multiselect={true} value={value} setValue={value => setValue(value)} />;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const SearchQuery: Story = {
|
|
112
|
+
render: ({ options, ...args }) => {
|
|
113
|
+
const [value, setValue] = useState<Array<string>>([]);
|
|
114
|
+
const [searchQueryValue, setSearchQueryValue] = useState<string>('');
|
|
115
|
+
|
|
44
116
|
return (
|
|
45
|
-
<Select
|
|
117
|
+
<Select<string>
|
|
46
118
|
{...args}
|
|
47
|
-
options={
|
|
48
|
-
|
|
49
|
-
'B',
|
|
50
|
-
'C',
|
|
51
|
-
'D',
|
|
52
|
-
'E',
|
|
53
|
-
'F',
|
|
54
|
-
'H',
|
|
55
|
-
'I',
|
|
56
|
-
'J',
|
|
57
|
-
'K',
|
|
58
|
-
'L',
|
|
59
|
-
'M',
|
|
60
|
-
'N',
|
|
61
|
-
'O',
|
|
62
|
-
'P',
|
|
63
|
-
'Q',
|
|
64
|
-
'R',
|
|
65
|
-
'S',
|
|
66
|
-
'T',
|
|
67
|
-
'U',
|
|
68
|
-
'V',
|
|
69
|
-
'W',
|
|
70
|
-
'X',
|
|
71
|
-
'Y',
|
|
72
|
-
'Z',
|
|
73
|
-
]}
|
|
119
|
+
options={options.filter(option => option.toLowerCase().includes(searchQueryValue.toLowerCase()))}
|
|
120
|
+
multiselect={true}
|
|
74
121
|
value={value}
|
|
75
|
-
renderOption={v => v}
|
|
76
|
-
optionVariant={undefined}
|
|
77
|
-
multiselect={false}
|
|
78
122
|
setValue={value => setValue(value)}
|
|
123
|
+
searchQueryPlaceholder='Search option'
|
|
124
|
+
searchQueryValue={searchQueryValue}
|
|
125
|
+
onSearchQuery={setSearchQueryValue}
|
|
79
126
|
/>
|
|
80
127
|
);
|
|
81
128
|
},
|
|
82
129
|
};
|
|
83
130
|
|
|
84
|
-
export
|
|
85
|
-
|
|
131
|
+
export const SearchQueryWithClearOption: Story = {
|
|
132
|
+
render: ({ options, ...args }) => {
|
|
133
|
+
const [value, setValue] = useState<Array<string>>([]);
|
|
134
|
+
const [searchQueryValue, setSearchQueryValue] = useState<string>('');
|
|
86
135
|
|
|
87
|
-
|
|
88
|
-
|
|
136
|
+
return (
|
|
137
|
+
<Select<string>
|
|
138
|
+
{...args}
|
|
139
|
+
options={options.filter(option => option.toLowerCase().includes(searchQueryValue.toLowerCase()))}
|
|
140
|
+
multiselect={true}
|
|
141
|
+
value={value}
|
|
142
|
+
showClearOption={true}
|
|
143
|
+
setValue={value => setValue(value)}
|
|
144
|
+
searchQueryPlaceholder='Search option'
|
|
145
|
+
searchQueryValue={searchQueryValue}
|
|
146
|
+
onSearchQuery={setSearchQueryValue}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
},
|
|
89
150
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import styled from 'styled-components';
|
|
2
2
|
|
|
3
|
+
import { Menu } from '@polpo/ui';
|
|
4
|
+
|
|
3
5
|
export const SelectStyle = styled.section`
|
|
4
6
|
cursor: pointer;
|
|
5
7
|
|
|
@@ -57,106 +59,22 @@ export const SelectStyle = styled.section`
|
|
|
57
59
|
}
|
|
58
60
|
`;
|
|
59
61
|
|
|
60
|
-
export const
|
|
61
|
-
|
|
62
|
-
border-radius: 4px;
|
|
63
|
-
border: 1px solid ${props => props.theme.colors.primary.main};
|
|
64
|
-
box-shadow: 0 3px 7px 0 rgba(145, 145, 145, 0.13);
|
|
65
|
-
overflow: auto;
|
|
66
|
-
background: ${props => props.theme.colors.background.main};
|
|
67
|
-
color: ${props => props.theme.colors.text.main};
|
|
68
|
-
display: grid;
|
|
69
|
-
gap: 5px;
|
|
70
|
-
align-content: start;
|
|
71
|
-
outline: 0;
|
|
72
|
-
top: 50%;
|
|
73
|
-
left: 50%;
|
|
74
|
-
transform: translate(-50%, -50%);
|
|
75
|
-
width: 80vw;
|
|
76
|
-
max-height: 80dvh;
|
|
77
|
-
min-width: fit-content;
|
|
78
|
-
|
|
79
|
-
.options-list-container {
|
|
80
|
-
height: 100%;
|
|
81
|
-
overflow: auto;
|
|
82
|
-
outline: 0;
|
|
83
|
-
}
|
|
62
|
+
export const OptionsMenuStyle = styled(Menu)`
|
|
63
|
+
overflow-y: auto;
|
|
84
64
|
|
|
85
|
-
|
|
86
|
-
list-style: none;
|
|
65
|
+
&.search-input {
|
|
87
66
|
display: grid;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
outline: 0;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.options-selected {
|
|
94
|
-
border-bottom: 1px solid #9e9e9e;
|
|
95
|
-
padding-bottom: 5px;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.option-empty {
|
|
99
|
-
color: #919191;
|
|
100
|
-
}
|
|
101
|
-
`;
|
|
102
|
-
|
|
103
|
-
export const OptionStyle = styled.li`
|
|
104
|
-
padding: 0.5em 1em;
|
|
105
|
-
font-size: 0.8em;
|
|
106
|
-
white-space: nowrap;
|
|
107
|
-
text-overflow: ellipsis;
|
|
108
|
-
overflow: hidden;
|
|
109
|
-
display: grid;
|
|
110
|
-
grid-template-columns: 1fr;
|
|
111
|
-
gap: 1em;
|
|
112
|
-
align-items: center;
|
|
113
|
-
cursor: pointer;
|
|
114
|
-
outline: 0;
|
|
115
|
-
|
|
116
|
-
&.multiselect-icon {
|
|
117
|
-
grid-template-columns: 1fr auto;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
&.multiselect-checkbox {
|
|
121
|
-
grid-template-columns: auto 1fr;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.option-content {
|
|
125
|
-
white-space: nowrap;
|
|
126
|
-
text-overflow: ellipsis;
|
|
127
|
-
overflow: hidden;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
&:hover {
|
|
131
|
-
background: ${props => props.theme.colors.text.main}11;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
&:focus {
|
|
135
|
-
background: ${props => props.theme.colors.text.main}22;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
&.selected-option,
|
|
139
|
-
&[aria-selected='true'] {
|
|
140
|
-
background: ${props => props.theme.colors.primary.main}33;
|
|
141
|
-
|
|
142
|
-
&:hover {
|
|
143
|
-
background: ${props => props.theme.colors.primary.main}22;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
&:focus {
|
|
147
|
-
background: ${props => props.theme.colors.primary.main}11;
|
|
148
|
-
}
|
|
67
|
+
grid-template-rows: auto 1fr;
|
|
68
|
+
grid-template-columns: 1fr;
|
|
149
69
|
}
|
|
150
70
|
`;
|
|
151
71
|
|
|
152
72
|
export const OptionsHeaderStyle = styled.section`
|
|
153
|
-
|
|
154
|
-
gap: 5px;
|
|
155
|
-
padding-bottom: 5px;
|
|
73
|
+
margin: 5px;
|
|
156
74
|
|
|
157
75
|
.input-search {
|
|
158
|
-
padding:
|
|
159
|
-
border: 1px solid
|
|
76
|
+
padding: 0.8em 1em;
|
|
77
|
+
border: 1px solid ${props => props.theme.colors.border.main};
|
|
160
78
|
font-size: 0.8em;
|
|
161
79
|
width: 100%;
|
|
162
80
|
border-radius: 4px;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useMemo } from 'react';
|
|
2
|
-
import { useTheme } from 'styled-components';
|
|
1
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
2
|
|
|
4
3
|
import { Icon } from '../../icon';
|
|
5
4
|
import { Typography } from '../../typography';
|
|
@@ -15,23 +14,12 @@ import {
|
|
|
15
14
|
MultiValue,
|
|
16
15
|
OptionComponentProps,
|
|
17
16
|
SelectItem,
|
|
18
|
-
SelectOptionVariant,
|
|
19
17
|
SelectValue,
|
|
20
18
|
SingleSelectProps,
|
|
21
19
|
SingleValue,
|
|
22
20
|
UnControlledSelectProps,
|
|
23
21
|
} from './select.types';
|
|
24
22
|
|
|
25
|
-
import { useMediaQuery, useModalInContainer } from '@polpo/hooks';
|
|
26
|
-
|
|
27
|
-
/*
|
|
28
|
-
*type SelectContextState<T extends SelectItem = unknown> = {
|
|
29
|
-
*value: T;
|
|
30
|
-
*};
|
|
31
|
-
*
|
|
32
|
-
*const SelectContext = createContext<SelectContextState | null>(null);
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
23
|
export const Select = <T extends SelectItem>({
|
|
36
24
|
// Select props
|
|
37
25
|
options,
|
|
@@ -45,7 +33,6 @@ export const Select = <T extends SelectItem>({
|
|
|
45
33
|
hasNextPage,
|
|
46
34
|
multiselect,
|
|
47
35
|
maxOptions,
|
|
48
|
-
optionVariant = SelectOptionVariant.DEFAULT,
|
|
49
36
|
// Shared props
|
|
50
37
|
name,
|
|
51
38
|
value,
|
|
@@ -74,19 +61,18 @@ export const Select = <T extends SelectItem>({
|
|
|
74
61
|
...fieldProps
|
|
75
62
|
}: UnControlledSelectProps<T>) => {
|
|
76
63
|
const id = useMemo(() => crypto.randomUUID(), []);
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const { modalRef, isVisible, setIsVisible, modalStyle, containerRef } = useModalInContainer({
|
|
80
|
-
position: 'bottom',
|
|
81
|
-
distancePercentage: 50,
|
|
82
|
-
offset: 5,
|
|
83
|
-
});
|
|
64
|
+
const containerRef = useRef<HTMLElement>(null);
|
|
65
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
84
66
|
|
|
85
67
|
const openSelect = useCallback(
|
|
86
68
|
(open: boolean) => {
|
|
87
|
-
|
|
69
|
+
if (open && !disabled) {
|
|
70
|
+
setIsOpen(true);
|
|
71
|
+
} else {
|
|
72
|
+
setIsOpen(false);
|
|
73
|
+
}
|
|
88
74
|
},
|
|
89
|
-
[disabled
|
|
75
|
+
[disabled],
|
|
90
76
|
);
|
|
91
77
|
|
|
92
78
|
const compareValuesIsEqual = useCallback(
|
|
@@ -167,10 +153,10 @@ export const Select = <T extends SelectItem>({
|
|
|
167
153
|
setValue(filteredValues.length === 0 ? [] : filteredValues);
|
|
168
154
|
} else {
|
|
169
155
|
setValue(null);
|
|
170
|
-
|
|
156
|
+
setIsOpen(false);
|
|
171
157
|
}
|
|
172
158
|
},
|
|
173
|
-
[compareValuesIsEqual, multiselect,
|
|
159
|
+
[compareValuesIsEqual, multiselect, setValue, value],
|
|
174
160
|
);
|
|
175
161
|
|
|
176
162
|
const selectOption = useCallback(
|
|
@@ -183,10 +169,10 @@ export const Select = <T extends SelectItem>({
|
|
|
183
169
|
setValue([...(value as Array<T>), selectedOption] as MultiValue<T>);
|
|
184
170
|
} else {
|
|
185
171
|
setValue(selectedOption as SingleValue<T>);
|
|
186
|
-
|
|
172
|
+
setIsOpen(false);
|
|
187
173
|
}
|
|
188
174
|
},
|
|
189
|
-
[maxOptions, multiselect,
|
|
175
|
+
[maxOptions, multiselect, setValue, value],
|
|
190
176
|
);
|
|
191
177
|
|
|
192
178
|
const clearOption = useCallback(
|
|
@@ -214,7 +200,7 @@ export const Select = <T extends SelectItem>({
|
|
|
214
200
|
<Field
|
|
215
201
|
id={id}
|
|
216
202
|
error={error}
|
|
217
|
-
isFocus={
|
|
203
|
+
isFocus={isOpen}
|
|
218
204
|
onClickLeftIcon={() => openSelect(true)}
|
|
219
205
|
onClickRightIcon={() => openSelect(true)}
|
|
220
206
|
ref={containerRef}
|
|
@@ -229,7 +215,7 @@ export const Select = <T extends SelectItem>({
|
|
|
229
215
|
type='button'
|
|
230
216
|
className={`input-button ${(Array.isArray(value) ? value.length > 0 : value) ? '' : 'placeholder'}`}
|
|
231
217
|
aria-haspopup='listbox'
|
|
232
|
-
aria-expanded={
|
|
218
|
+
aria-expanded={isOpen}
|
|
233
219
|
onFocus={e => {
|
|
234
220
|
openSelect(true);
|
|
235
221
|
onFocus && onFocus(e);
|
|
@@ -246,11 +232,12 @@ export const Select = <T extends SelectItem>({
|
|
|
246
232
|
<Icon name='cross' />
|
|
247
233
|
</section>
|
|
248
234
|
)}
|
|
249
|
-
<Icon name={
|
|
235
|
+
<Icon name={isOpen ? 'caret-up' : 'caret-down'} />
|
|
250
236
|
</section>
|
|
251
237
|
<Options
|
|
252
|
-
|
|
253
|
-
isOpen={
|
|
238
|
+
containerRef={containerRef}
|
|
239
|
+
isOpen={isOpen}
|
|
240
|
+
onClose={() => setIsOpen(false)}
|
|
254
241
|
value={value}
|
|
255
242
|
compareValueOrValuesAreEqual={compareValueOrValuesAreEqual}
|
|
256
243
|
Component={OptionComponent}
|
|
@@ -260,20 +247,10 @@ export const Select = <T extends SelectItem>({
|
|
|
260
247
|
loadMore={loadMore}
|
|
261
248
|
searchQueryValue={searchQueryValue}
|
|
262
249
|
onSearchQuery={onSearchQuery}
|
|
263
|
-
style={
|
|
264
|
-
(isMobile && {
|
|
265
|
-
width: containerRef.current?.offsetWidth,
|
|
266
|
-
transform: 'none',
|
|
267
|
-
maxHeight: '250px',
|
|
268
|
-
...modalStyle,
|
|
269
|
-
}) ||
|
|
270
|
-
{}
|
|
271
|
-
}
|
|
272
250
|
searchQueryPlaceholder={searchQueryPlaceholder}
|
|
273
251
|
options={options}
|
|
274
252
|
selectOption={selectOption}
|
|
275
253
|
unselectOption={unSelectOption}
|
|
276
|
-
variant={optionVariant}
|
|
277
254
|
/>
|
|
278
255
|
</SelectStyle>
|
|
279
256
|
</Field>
|