@qoretechnologies/reqore 0.42.0 → 0.44.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.
Files changed (46) hide show
  1. package/dist/components/Button/index.d.ts +1 -0
  2. package/dist/components/Button/index.d.ts.map +1 -1
  3. package/dist/components/Button/index.js +6 -2
  4. package/dist/components/Button/index.js.map +1 -1
  5. package/dist/components/ControlGroup/index.d.ts +1 -0
  6. package/dist/components/ControlGroup/index.d.ts.map +1 -1
  7. package/dist/components/ControlGroup/index.js +18 -4
  8. package/dist/components/ControlGroup/index.js.map +1 -1
  9. package/dist/components/DatePicker/index.d.ts +29 -0
  10. package/dist/components/DatePicker/index.d.ts.map +1 -0
  11. package/dist/components/DatePicker/index.js +147 -0
  12. package/dist/components/DatePicker/index.js.map +1 -0
  13. package/dist/components/Effect/index.d.ts +1 -1
  14. package/dist/components/Effect/index.d.ts.map +1 -1
  15. package/dist/components/Effect/index.js.map +1 -1
  16. package/dist/components/Input/index.d.ts +3 -1
  17. package/dist/components/Input/index.d.ts.map +1 -1
  18. package/dist/components/Input/index.js +5 -1
  19. package/dist/components/Input/index.js.map +1 -1
  20. package/dist/components/InternalPopover/index.d.ts +2 -0
  21. package/dist/components/InternalPopover/index.d.ts.map +1 -1
  22. package/dist/components/InternalPopover/index.js +5 -4
  23. package/dist/components/InternalPopover/index.js.map +1 -1
  24. package/dist/components/Slider/index.d.ts +40 -0
  25. package/dist/components/Slider/index.d.ts.map +1 -0
  26. package/dist/components/Slider/index.js +148 -0
  27. package/dist/components/Slider/index.js.map +1 -0
  28. package/dist/components/Tag/index.js.map +1 -1
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +2 -0
  32. package/dist/index.js.map +1 -1
  33. package/package.json +5 -1
  34. package/src/components/Button/index.tsx +7 -2
  35. package/src/components/ControlGroup/index.tsx +19 -3
  36. package/src/components/DatePicker/index.tsx +347 -0
  37. package/src/components/Effect/index.tsx +1 -0
  38. package/src/components/Input/index.tsx +18 -5
  39. package/src/components/InternalPopover/index.tsx +10 -10
  40. package/src/components/Slider/index.tsx +358 -0
  41. package/src/components/Tag/index.tsx +4 -4
  42. package/src/index.tsx +2 -0
  43. package/src/stories/DatePicker/DatePicker.stories.tsx +348 -0
  44. package/src/stories/Dropdown/Dropdown.stories.tsx +3 -3
  45. package/src/stories/Slider/Slider.stories.tsx +173 -0
  46. package/tests.json +1 -1
@@ -0,0 +1,347 @@
1
+ import {
2
+ getLocalTimeZone,
3
+ isSameDay,
4
+ parseAbsoluteToLocal,
5
+ Time,
6
+ toCalendarDateTime,
7
+ toZoned,
8
+ ZonedDateTime,
9
+ } from '@internationalized/date';
10
+ import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
11
+ import {
12
+ Button,
13
+ Calendar,
14
+ CalendarCell,
15
+ CalendarGrid,
16
+ DateInput,
17
+ DatePicker as RADatePicker,
18
+ DatePickerProps,
19
+ DateSegment,
20
+ HeadingContext,
21
+ HeadingProps,
22
+ TimeField,
23
+ useContextProps,
24
+ } from 'react-aria-components';
25
+ import styled from 'styled-components';
26
+ import { ReqorePanel, ReqorePopover } from '../..';
27
+ import { changeLightness } from '../../helpers/colors';
28
+ import { IPopoverControls } from '../../hooks/usePopover';
29
+ import { useReqoreTheme } from '../../hooks/useTheme';
30
+ import { useTooltip } from '../../hooks/useTooltip';
31
+ import { DisabledElement } from '../../styles';
32
+ import {
33
+ IReqoreIntent,
34
+ IWithReqoreCustomTheme,
35
+ IWithReqoreFlat,
36
+ IWithReqoreFluid,
37
+ IWithReqoreMinimal,
38
+ IWithReqoreSize,
39
+ IWithReqoreTooltip,
40
+ TReqoreTooltipProp,
41
+ } from '../../types/global';
42
+ import ReqoreButton, { IReqoreButtonProps } from '../Button';
43
+ import ReqoreControlGroup from '../ControlGroup';
44
+ import { IReqoreTextEffectProps } from '../Effect';
45
+ import ReqoreInput from '../Input';
46
+ import { IReqorePanelProps } from '../Panel';
47
+ import { IReqorePopoverProps } from '../Popover';
48
+
49
+ type TDateValue = string | Date | null;
50
+ export interface IDatePickerProps<T extends TDateValue>
51
+ extends Omit<DatePickerProps<ZonedDateTime>, 'value' | 'onChange' | 'defaultValue'>,
52
+ IWithReqoreSize,
53
+ IWithReqoreTooltip,
54
+ IWithReqoreFlat,
55
+ IWithReqoreMinimal,
56
+ IWithReqoreFluid,
57
+ IWithReqoreCustomTheme,
58
+ IReqoreIntent {
59
+ value: T;
60
+ onChange(value: T): void;
61
+
62
+ rounded?: boolean;
63
+ pill?: boolean;
64
+ isClearable?: boolean;
65
+ onClearClick?(): void;
66
+
67
+ closeOnSelect?: boolean;
68
+
69
+ popoverProps?: Partial<IReqorePopoverProps>;
70
+ inputProps?: IReqoreTextEffectProps;
71
+ timeInputProps?: IReqoreTextEffectProps;
72
+ pickerProps?: IReqorePanelProps;
73
+ calendarProps?: React.ComponentProps<typeof Calendar>;
74
+ timeFieldProps?: React.ComponentProps<typeof TimeField<Time>>;
75
+ pickerDayProps?: IReqoreButtonProps;
76
+ pickerActiveDayProps?: IReqoreButtonProps;
77
+ }
78
+
79
+ const StyledRADatePicker: typeof RADatePicker = styled(RADatePicker)`
80
+ &[data-fluid='false'] {
81
+ min-width: 220px;
82
+ width: fit-content;
83
+ }
84
+ `;
85
+ const StyledDateSegment: typeof DateSegment = styled(DateSegment)`
86
+ padding: 2px;
87
+
88
+ &:focus {
89
+ background-color: ${(props) => changeLightness(props.theme.main, 0.1)};
90
+ outline: none;
91
+ border-radius: 4px;
92
+ }
93
+ `;
94
+ const StyledDateInput: typeof DateInput = styled(DateInput)`
95
+ display: inline-flex;
96
+ align-items: center;
97
+ `;
98
+
99
+ const StyledTimeField: typeof TimeField = styled(TimeField)`
100
+ display: flex;
101
+ flex: 1 auto;
102
+ `;
103
+
104
+ // utility to convert date to ZonedDateTime because datepicker can't use Date
105
+ const toDate = (date?: Date | string) => {
106
+ if (date) {
107
+ return parseAbsoluteToLocal(typeof date === 'string' ? date : date.toISOString());
108
+ }
109
+ return undefined;
110
+ };
111
+
112
+ const StyledCalendarCell: typeof CalendarCell = styled(CalendarCell)`
113
+ &[data-disabled='true']:not([data-selected='true']) {
114
+ ${DisabledElement}
115
+ }
116
+ &:focus {
117
+ outline: none;
118
+ }
119
+ `;
120
+ const Heading = (props: HeadingProps) => {
121
+ [props] = useContextProps(props, undefined, HeadingContext);
122
+
123
+ return <>{props.children}</>;
124
+ };
125
+ const DatePickerTooltip = ({
126
+ targetElement,
127
+ tooltip,
128
+ }: {
129
+ targetElement: HTMLElement | undefined;
130
+ tooltip?: TReqoreTooltipProp;
131
+ }) => {
132
+ useTooltip(targetElement, tooltip);
133
+
134
+ return null;
135
+ };
136
+
137
+ export const DatePicker = <T extends TDateValue>({
138
+ value: _value,
139
+ onChange,
140
+ fluid = true,
141
+ flat,
142
+ rounded,
143
+ minimal,
144
+ size = 'normal',
145
+ pill,
146
+ intent,
147
+ customTheme,
148
+ granularity = 'minute',
149
+ hourCycle = 24,
150
+ hideTimeZone = true,
151
+ shouldForceLeadingZeros = true,
152
+ onClearClick,
153
+ closeOnSelect = true,
154
+ tooltip,
155
+ popoverProps,
156
+ inputProps,
157
+ pickerProps,
158
+ timeInputProps,
159
+ timeFieldProps,
160
+ pickerActiveDayProps,
161
+ pickerDayProps,
162
+ ...props
163
+ }: IDatePickerProps<T>) => {
164
+ const value = useMemo(() => (_value ? toDate(_value) : null), [_value]);
165
+ // save time in separate state because date can be cleared and equals to null
166
+ const [time, setTime] = useState<Time>(() => {
167
+ if (!value) return undefined;
168
+ return new Time(
169
+ value?.hour ?? 0,
170
+ value?.minute ?? 0,
171
+ value?.second ?? 0,
172
+ value?.millisecond ?? 0
173
+ );
174
+ });
175
+ const theme = useReqoreTheme('main', customTheme, intent);
176
+ const popoverData = useRef({} as IPopoverControls);
177
+ const [containerRef, setContainerRef] = useState<HTMLElement>(undefined);
178
+ const showTime = granularity === 'minute' || granularity === 'second' || granularity === 'hour';
179
+ // use ref to save value type since datepicker can have null values
180
+ const isStringRef = useRef(typeof _value === 'string');
181
+ useLayoutEffect(() => {
182
+ if (value) isStringRef.current = typeof _value === 'string';
183
+ });
184
+
185
+ const handleDateChange: DatePickerProps<ZonedDateTime>['onChange'] = (value) => {
186
+ let date: Date;
187
+ // if previous value is null apply saved time state
188
+ if (!_value && time) {
189
+ date = toZoned(toCalendarDateTime(value, time), getLocalTimeZone()).toDate();
190
+ } else {
191
+ // set date and time from changed value
192
+ date = value ? value.toDate() : null;
193
+ if (date) setTime(new Time(value?.hour, value?.minute, value?.second, value?.millisecond));
194
+ }
195
+ onChange?.((isStringRef.current ? date?.toISOString() : date) as T);
196
+
197
+ if (closeOnSelect && !showTime) {
198
+ popoverData.current?.close();
199
+ }
200
+ };
201
+ const onTimeChange = (time: Time | null) => {
202
+ if (!time) return;
203
+
204
+ setTime(time);
205
+ if (value) {
206
+ const date = toZoned(toCalendarDateTime(value, time), getLocalTimeZone());
207
+ handleDateChange?.(date);
208
+ }
209
+ };
210
+ const handleClearClick = () => {
211
+ if (value) onChange(null);
212
+ if (time) setTime(new Time(0, 0, 0, 0));
213
+ onClearClick?.();
214
+ };
215
+
216
+ return (
217
+ <StyledRADatePicker
218
+ value={value}
219
+ onChange={handleDateChange}
220
+ granularity={granularity}
221
+ hideTimeZone={hideTimeZone}
222
+ shouldForceLeadingZeros={shouldForceLeadingZeros}
223
+ hourCycle={hourCycle}
224
+ data-fluid={fluid}
225
+ aria-label='Date'
226
+ ref={(node) => setContainerRef(node)}
227
+ {...props}
228
+ >
229
+ <DatePickerTooltip targetElement={containerRef} tooltip={tooltip} />
230
+ <ReqorePopover
231
+ component={ReqoreInput}
232
+ componentProps={{
233
+ as: StyledDateInput,
234
+ onClearClick: handleClearClick,
235
+ fluid,
236
+ value,
237
+ rounded,
238
+ size,
239
+ pill,
240
+ minimal,
241
+ intent,
242
+ flat,
243
+ icon: 'CalendarLine',
244
+ ...inputProps,
245
+ }}
246
+ passPopoverData={(data) => (popoverData.current = data)}
247
+ isReqoreComponent
248
+ noWrapper
249
+ handler='click'
250
+ placement='bottom-start'
251
+ noArrow
252
+ {...popoverProps}
253
+ content={
254
+ <Calendar<ZonedDateTime> value={value} onChange={handleDateChange}>
255
+ <ReqorePanel
256
+ minimal
257
+ size='small'
258
+ responsiveTitle={false}
259
+ intent={intent}
260
+ label={<Heading />}
261
+ {...pickerProps}
262
+ actions={[
263
+ {
264
+ as: ReqoreButton,
265
+ props: {
266
+ as: Button,
267
+ customTheme: theme,
268
+ slot: 'previous',
269
+ icon: 'ArrowLeftFill',
270
+ },
271
+ },
272
+ {
273
+ as: ReqoreButton,
274
+ props: {
275
+ as: Button,
276
+ customTheme: theme,
277
+ slot: 'next',
278
+ icon: 'ArrowRightFill',
279
+ },
280
+ },
281
+ ]}
282
+ >
283
+ <CalendarGrid>
284
+ {(date) => {
285
+ const isSelected = value && isSameDay(date, value);
286
+ return (
287
+ <StyledCalendarCell
288
+ data-selected={isSelected}
289
+ date={date}
290
+ key={date.toString()}
291
+ >
292
+ <ReqoreButton
293
+ key={date.toString()}
294
+ customTheme={isSelected ? theme : { main: 'transparent' }}
295
+ label={date.day}
296
+ onClick={() => handleDateChange(toZoned(date, getLocalTimeZone()))}
297
+ active={isSelected}
298
+ textAlign='center'
299
+ circle
300
+ minimal
301
+ flat
302
+ compact
303
+ {...(isSelected ? pickerActiveDayProps : pickerDayProps)}
304
+ />
305
+ </StyledCalendarCell>
306
+ );
307
+ }}
308
+ </CalendarGrid>
309
+ {showTime && (
310
+ <ReqoreControlGroup fluid>
311
+ <StyledTimeField
312
+ value={time}
313
+ onChange={onTimeChange}
314
+ granularity={granularity}
315
+ hideTimeZone={hideTimeZone}
316
+ shouldForceLeadingZeros={shouldForceLeadingZeros}
317
+ hourCycle={hourCycle}
318
+ aria-label='Time'
319
+ {...timeFieldProps}
320
+ >
321
+ <ReqoreInput
322
+ icon='TimeLine'
323
+ fluid
324
+ as={StyledDateInput}
325
+ flat={flat}
326
+ rounded={rounded}
327
+ minimal={minimal}
328
+ size={size}
329
+ pill={pill}
330
+ intent={intent}
331
+ theme={theme}
332
+ {...timeInputProps}
333
+ >
334
+ {(segment) => <StyledDateSegment segment={segment} />}
335
+ </ReqoreInput>
336
+ </StyledTimeField>
337
+ </ReqoreControlGroup>
338
+ )}
339
+ </ReqorePanel>
340
+ </Calendar>
341
+ }
342
+ >
343
+ {(segment) => <StyledDateSegment segment={segment} />}
344
+ </ReqorePopover>
345
+ </StyledRADatePicker>
346
+ );
347
+ };
@@ -47,6 +47,7 @@ export type TReqoreEffectColorManipulationMultiplier =
47
47
  | 29
48
48
  | 30;
49
49
  export type TReqoreEffectColorManipulationAlpha =
50
+ | 0
50
51
  | 0.1
51
52
  | 0.2
52
53
  | 0.3
@@ -31,7 +31,7 @@ import ReqoreIcon, { IReqoreIconProps } from '../Icon';
31
31
  import ReqoreInputClearButton from '../InputClearButton';
32
32
 
33
33
  export interface IReqoreInputProps
34
- extends React.HTMLAttributes<HTMLInputElement>,
34
+ extends Omit<React.ComponentPropsWithoutRef<'input'>, 'size' | 'children'>,
35
35
  IReqoreDisabled,
36
36
  IReqoreReadOnly,
37
37
  IReqoreIntent,
@@ -63,6 +63,9 @@ export interface IReqoreInputProps
63
63
  rightIconProps?: IReqoreIconProps;
64
64
 
65
65
  pill?: boolean;
66
+
67
+ children?: React.ReactNode | ((props: any) => React.ReactNode);
68
+ as?: string | React.ElementType;
66
69
  }
67
70
 
68
71
  export interface IReqoreInputStyle extends IReqoreInputProps {
@@ -242,13 +245,13 @@ const ReqoreInput = forwardRef<HTMLDivElement, IReqoreInputProps>(
242
245
  </StyledIconWrapper>
243
246
  )}
244
247
  <StyledInput
248
+ as='input'
245
249
  {...omit(rest, ['children'])}
246
250
  effect={{
247
251
  interactive: !rest?.disabled && !readOnly,
248
252
  ...rest?.effect,
249
253
  }}
250
254
  onChange={!readOnly && !rest?.disabled ? rest?.onChange : undefined}
251
- as='input'
252
255
  ref={(ref) => setInputRef(ref)}
253
256
  theme={theme}
254
257
  _size={size}
@@ -257,13 +260,23 @@ const ReqoreInput = forwardRef<HTMLDivElement, IReqoreInputProps>(
257
260
  rounded={rounded}
258
261
  hasIcon={!!icon}
259
262
  hasRightIcon={!!rightIcon}
260
- clearable={!rest?.disabled && !readOnly && !!(onClearClick && rest?.onChange)}
263
+ clearable={
264
+ !rest?.disabled &&
265
+ !readOnly &&
266
+ !!(onClearClick && (rest.as || rest.children || rest?.onChange))
267
+ }
261
268
  className={`${className || ''} reqore-control reqore-input`}
262
269
  readOnly={readOnly}
263
270
  pill={pill}
264
- />
271
+ >
272
+ {rest?.children}
273
+ </StyledInput>
265
274
  <ReqoreInputClearButton
266
- enabled={!readOnly && !rest?.disabled && !!(onClearClick && rest?.onChange)}
275
+ enabled={
276
+ !readOnly &&
277
+ !rest?.disabled &&
278
+ !!(onClearClick && (rest.as || rest.children || rest?.onChange))
279
+ }
267
280
  onClick={onClearClick}
268
281
  hasRightIcon={!!rightIcon}
269
282
  size={size}
@@ -30,15 +30,15 @@ const getPopoverArrowColor = ({ theme, dim, intent, flat, effect, isOpaque }) =>
30
30
  0.04
31
31
  )
32
32
  : intent
33
- ? changeLightness(getNotificationIntent(theme, intent), flat ? 0.1 : 0.2)
34
- : theme.popover?.main ||
35
- rgba(
36
- changeLightness(
37
- flat ? theme.main : getNotificationIntent(theme, intent),
38
- flat ? 0.1 : 0.2
33
+ ? changeLightness(getNotificationIntent(theme, intent), flat ? 0.1 : 0.2)
34
+ : theme.popover?.main ||
35
+ rgba(
36
+ changeLightness(
37
+ flat ? theme.main : getNotificationIntent(theme, intent),
38
+ flat ? 0.1 : 0.2
39
+ ),
40
+ isOpaque ? 1 : 0.3
39
41
  ),
40
- isOpaque ? 1 : 0.3
41
- ),
42
42
  dim ? 0.3 : 1
43
43
  );
44
44
 
@@ -58,7 +58,7 @@ const StyledPopoverArrow = styled.div<{ theme: IReqoreTheme }>`
58
58
  }
59
59
  `;
60
60
 
61
- const StyledPopoverWrapper = styled.div<{ theme: IReqoreTheme }>`
61
+ export const StyledPopoverWrapper = styled.div<{ theme: IReqoreTheme }>`
62
62
  animation: 0.2s ${fadeIn} ease-out;
63
63
  max-width: ${({ maxWidth }) => maxWidth};
64
64
  max-height: ${({ maxHeight }) => maxHeight};
@@ -131,7 +131,7 @@ const StyledPopoverWrapper = styled.div<{ theme: IReqoreTheme }>`
131
131
  }
132
132
  `;
133
133
 
134
- const StyledPopoverContent = styled.div`
134
+ export const StyledPopoverContent = styled.div`
135
135
  width: 100%;
136
136
  height: 100%;
137
137
  z-index: 20;