@povio/ui 0.1.0 → 2.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.
Files changed (39) hide show
  1. package/dist/components/Menu/MenuItem.js +2 -1
  2. package/dist/components/buttons/Button/button.cva.d.ts +5 -5
  3. package/dist/components/buttons/Button/button.cva.js +188 -214
  4. package/dist/components/buttons/InlineIconButton/InlineIconButton.js +1 -1
  5. package/dist/components/buttons/PillButton/pillButton.cva.d.ts +2 -0
  6. package/dist/components/buttons/PillButton/pillButton.cva.js +227 -45
  7. package/dist/components/buttons/SplitButton/SplitButton.js +2 -2
  8. package/dist/components/buttons/TextButton/TextButton.js +1 -1
  9. package/dist/components/inputs/Checkbox/CheckboxCheckmark.js +2 -19
  10. package/dist/components/inputs/Checkbox/checkbox.cva.js +4 -0
  11. package/dist/components/inputs/DateTime/shared/CalendarCell.js +3 -3
  12. package/dist/components/inputs/DateTime/shared/CalendarSelectHeader.js +1 -1
  13. package/dist/components/inputs/DateTime/shared/DatePickerInput.js +1 -1
  14. package/dist/components/inputs/DateTime/shared/RangeCalendar.js +6 -4
  15. package/dist/components/inputs/File/FileUpload.js +2 -2
  16. package/dist/components/inputs/File/shared/FileUploadContentLoading.js +3 -3
  17. package/dist/components/inputs/File/shared/InputUploadContent.js +1 -2
  18. package/dist/components/inputs/File/shared/ProgressBar.d.ts +6 -0
  19. package/dist/components/inputs/File/shared/ProgressBar.js +42 -0
  20. package/dist/components/inputs/File/shared/inputUploadButton.cva.js +2 -2
  21. package/dist/components/inputs/FormField/FormFieldHeader.js +1 -1
  22. package/dist/components/inputs/RadioGroup/RadioGroup.js +1 -11
  23. package/dist/components/inputs/RadioGroup/radio.cva.js +12 -2
  24. package/dist/components/inputs/shared/input.cva.js +23 -17
  25. package/dist/components/inputs/shared/label.cva.js +9 -14
  26. package/dist/components/overlays/Modal/Modal.d.ts +2 -1
  27. package/dist/components/overlays/Modal/Modal.js +7 -3
  28. package/dist/components/overlays/Tooltip/tooltip.cva.js +4 -4
  29. package/dist/components/segment/SegmentItem.js +9 -8
  30. package/dist/components/shared/pagination/PaginationList.d.ts +1 -1
  31. package/dist/components/shared/pagination/minWidth.cva.d.ts +1 -1
  32. package/dist/components/status/Toast/Toast.js +2 -1
  33. package/dist/hooks/useFilters.d.ts +5 -1
  34. package/dist/hooks/useFilters.js +123 -69
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +2 -0
  37. package/package.json +1 -1
  38. package/dist/components/inputs/File/shared/FileProgressBar.d.ts +0 -3
  39. package/dist/components/inputs/File/shared/FileProgressBar.js +0 -32
@@ -1,6 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { mergeRefs } from "@react-aria/utils";
3
- import { clsx } from "clsx";
4
3
  import { RadioGroup as RadioGroup$1, Radio } from "react-aria-components";
5
4
  import { Controller } from "react-hook-form";
6
5
  import { FormField } from "../FormField/FormField.js";
@@ -62,16 +61,7 @@ const RadioGroupBase = (props) => {
62
61
  className: radioCva({
63
62
  variant,
64
63
  ...rest
65
- }),
66
- children: /* @__PURE__ */ jsx(
67
- "div",
68
- {
69
- className: clsx(
70
- "absolute hidden size-1 rounded-full group-selected:block",
71
- "bg-interactive-contained-primary-on-idle group-hover:bg-interactive-contained-primary-on-hover"
72
- )
73
- }
74
- )
64
+ })
75
65
  }
76
66
  ),
77
67
  /* @__PURE__ */ jsx(CheckContent, { typography: radioTypographyMap({ variant, ...rest }), children: option.label })
@@ -2,7 +2,12 @@ import { cva } from "class-variance-authority";
2
2
  import { uiGroupOutlineClass } from "../../outline.clsx.js";
3
3
  import { compoundMapper } from "../../../utils/compoundMapper.js";
4
4
  const radio = cva(
5
- ["flex items-center justify-center border-2", "relative size-3-5 rounded-full p-1-5", uiGroupOutlineClass],
5
+ [
6
+ "flex items-center justify-center border-2",
7
+ "relative size-3-5 rounded-full p-1-5",
8
+ "before:absolute before:hidden before:size-1 before:rounded-full",
9
+ uiGroupOutlineClass
10
+ ],
6
11
  {
7
12
  variants: {
8
13
  variant: {
@@ -17,6 +22,8 @@ const radio = cva(
17
22
  "group-selected:group-hover:border-interactive-contained-primary-hover",
18
23
  "group-selected:group-pressed:bg-interactive-contained-primary-pressed",
19
24
  "group-selected:group-pressed:border-interactive-contained-primary-pressed",
25
+ "group-selected:group-disabled:bg-interactive-contained-primary-disabled",
26
+ "group-selected:group-disabled:border-interactive-contained-primary-disabled",
20
27
  "group-indeterminate:border-interactive-contained-primary-idle",
21
28
  "group-indeterminate:bg-interactive-contained-primary-idle",
22
29
  "group-indeterminate:group-hover:bg-interactive-contained-primary-hover",
@@ -24,7 +31,10 @@ const radio = cva(
24
31
  "group-indeterminate:group-pressed:bg-interactive-contained-primary-pressed",
25
32
  "group-indeterminate:group-pressed:border-interactive-contained-primary-pressed",
26
33
  "group-invalid:border-interactive-contained-error-idle",
27
- "group-focus-visible:outline-interactive-contained-primary-focus"
34
+ "group-focus-visible:outline-interactive-contained-primary-focus",
35
+ "before:bg-interactive-contained-primary-on-idle",
36
+ "group-hover:before:bg-interactive-contained-primary-on-hover",
37
+ "group-selected:before:block"
28
38
  ]
29
39
  }
30
40
  },
@@ -10,7 +10,13 @@ const inputBase = cva([uiOutlineClass, "flex w-full truncate"], {
10
10
  },
11
11
  as: {
12
12
  default: "rounded-input-rounding-default",
13
- floating: "rounded-input-rounding-default",
13
+ floating: [
14
+ "rounded-input-rounding-default",
15
+ "py-floating-label-input-height-empty!",
16
+ "group-has-[input:not(:placeholder-shown)]/input-content:pb-floating-label-input-height-filled!",
17
+ "group-has-[input:not(:placeholder-shown)]/input-content:pt-[calc(var(--spacing-input-height-default)+14px)]!"
18
+ // TODO: remove this hardcoded value
19
+ ],
14
20
  filter: "rounded-input-rounding-default",
15
21
  inline: [
16
22
  "h-full",
@@ -110,10 +116,10 @@ const inputBase = cva([uiOutlineClass, "flex w-full truncate"], {
110
116
  const inputSize = cva("", {
111
117
  variants: {
112
118
  size: {
113
- "extra-small": "px-input-side-xs py-input-height-extra-small text-label-1",
114
- small: "px-input-side-s py-input-height-small text-label-1",
115
- default: "px-input-side-m py-input-height-default text-label-1",
116
- large: "px-input-side-l py-input-height-large text-label-1"
119
+ "extra-small": "px-input-side-default py-input-height-default text-label-1",
120
+ small: "px-input-side-default py-input-height-default text-label-1",
121
+ default: "px-input-side-default py-input-height-default text-label-1",
122
+ large: "px-input-side-default py-input-height-default text-label-1"
117
123
  }
118
124
  },
119
125
  defaultVariants: {
@@ -135,18 +141,18 @@ const inputSide = cva("", {
135
141
  }
136
142
  },
137
143
  compoundVariants: [
138
- { type: "left", size: "extra-small", className: "left-input-side-xs" },
139
- { type: "right", size: "extra-small", className: "right-input-side-xs" },
140
- { type: "var", size: "extra-small", className: "--spacing-input-side-xs" },
141
- { type: "left", size: "small", className: "left-input-side-s" },
142
- { type: "right", size: "small", className: "right-input-side-s" },
143
- { type: "var", size: "small", className: "--spacing-input-side-s" },
144
- { type: "left", size: "default", className: "left-input-side-m" },
145
- { type: "right", size: "default", className: "right-input-side-m" },
146
- { type: "var", size: "default", className: "--spacing-input-side-m" },
147
- { type: "left", size: "large", className: "left-input-side-l" },
148
- { type: "right", size: "large", className: "right-input-side-l" },
149
- { type: "var", size: "large", className: "--spacing-input-side-l" }
144
+ { type: "left", size: "extra-small", className: "left-input-side-default" },
145
+ { type: "right", size: "extra-small", className: "right-input-side-default" },
146
+ { type: "var", size: "extra-small", className: "--spacing-input-side-default" },
147
+ { type: "left", size: "small", className: "left-input-side-default" },
148
+ { type: "right", size: "small", className: "right-input-side-default" },
149
+ { type: "var", size: "small", className: "--spacing-input-side-default" },
150
+ { type: "left", size: "default", className: "left-input-side-default" },
151
+ { type: "right", size: "default", className: "right-input-side-default" },
152
+ { type: "var", size: "default", className: "--spacing-input-side-default" },
153
+ { type: "left", size: "large", className: "left-input-side-default" },
154
+ { type: "right", size: "large", className: "right-input-side-default" },
155
+ { type: "var", size: "large", className: "--spacing-input-side-default" }
150
156
  ],
151
157
  defaultVariants: {
152
158
  size: "default"
@@ -7,6 +7,7 @@ const labelBase = cva("text-text-default-1", {
7
7
  filter: ["-translate-y-1/2 absolute top-1/2"],
8
8
  inline: ["flex items-start gap-1"],
9
9
  floating: [
10
+ "pointer-events-none",
10
11
  "absolute transition-all duration-75",
11
12
  "-translate-y-1/2 top-1/2",
12
13
  "text-text-default-2 group-has-[input:disabled]/input-content:text-interactive-text-secondary-disabled",
@@ -19,17 +20,11 @@ const labelBase = cva("text-text-default-1", {
19
20
  // TextEditor default positioning
20
21
  "group-data-[text-editor]/text-editor:top-5 group-data-[text-editor]/text-editor:left-2 group-data-[text-editor]/text-editor:translate-y-0",
21
22
  // Text and Number Input
22
- "group-focus/input-content:top-1 group-focus/input-content:translate-y-0",
23
- "group-focus-within/input-content:top-1 group-focus-within/input-content:translate-y-0",
24
- "group-has-[input:not(:placeholder-shown)]/input-content:top-1 group-has-[input:not(:placeholder-shown)]/input-content:translate-y-0",
23
+ "group-has-[input:not(:placeholder-shown)]/input-content:top-floating-label-input-height-filled group-has-[input:not(:placeholder-shown)]/input-content:translate-y-0",
25
24
  // TextArea
26
- "group-focus/text-area:top-1 group-focus/text-area:translate-y-0",
27
- "group-focus-within/text-area:top-1 group-focus-within/text-area:translate-y-0",
28
- "group-has-[textarea:not(:placeholder-shown)]/text-area:top-1 group-has-[textarea:not(:placeholder-shown)]/text-area:translate-y-0",
25
+ "group-has-[textarea:not(:placeholder-shown)]/text-area:top-floating-label-input-height-filled group-has-[textarea:not(:placeholder-shown)]/text-area:translate-y-0",
29
26
  // TextEditor
30
- "group-focus/text-editor:top-1 group-focus/text-editor:translate-y-0",
31
- "group-focus-within/text-editor:top-1 group-focus-within/text-editor:translate-y-0",
32
- "group-data-[is-filled=true]/text-editor:top-1 group-data-[is-filled=true]/text-editor:translate-y-0",
27
+ "group-data-[is-filled=true]/text-editor:top-floating-label-input-height-filled group-data-[is-filled=true]/text-editor:translate-y-0",
33
28
  // Date and Time Picker
34
29
  "group-data-[has-selection]/date-picker-content:top-1 group-data-[has-selection]/date-picker-content:translate-y-0",
35
30
  "group-focus-within/date-picker-content:top-1 group-focus-within/date-picker-content:translate-y-0",
@@ -40,12 +35,12 @@ const labelBase = cva("text-text-default-1", {
40
35
  // Text and Number Input
41
36
  "group-has-[input:placeholder-shown]/input-content:text-text-default-3",
42
37
  "group-has-[input:placeholder-shown]/input-content:!font-medium",
43
- "group-has-[input:not(placeholder-shown)]/input-content:!font-semibold",
38
+ "group-has-[input:not(:placeholder-shown)]/input-content:!font-semibold",
44
39
  "group-has-[input:not(:placeholder-shown)]/input-content:!text-label-3",
45
- "group-has-[input:not(placeholder-shown)]/input-content:text-text-default-1",
46
- "group-focus-within/input-content:!text-text-default-1",
47
- "group-focus-within/input-content:text-label-3",
48
- "group-has-[input:focus-within]/input-content:!font-semibold",
40
+ "group-has-[input:not(:placeholder-shown)]/input-content:text-text-default-1",
41
+ // "group-focus-within/input-content:!text-text-default-1",
42
+ // "group-focus-within/input-content:text-label-3",
43
+ // "group-has-[input:focus-within]/input-content:!font-semibold",
49
44
  // TextArea
50
45
  "group-has-[textarea:placeholder-shown]/text-area:text-text-default-3",
51
46
  "group-has-[textarea:placeholder-shown]/text-area:!font-medium",
@@ -5,7 +5,8 @@ export interface ModalProps extends ModalVariantProps {
5
5
  portalContainerRef?: RefObject<HTMLElement>;
6
6
  onClose: () => void;
7
7
  modalClassName?: string;
8
+ closeIconClassName?: string;
8
9
  showCloseIcon?: boolean;
9
10
  isDismissable?: boolean;
10
11
  }
11
- export declare const Modal: ({ isOpen, portalContainerRef, onClose, aside, children, modalClassName, showCloseIcon, isDismissable, }: PropsWithChildren<ModalProps>) => import("react/jsx-runtime").JSX.Element;
12
+ export declare const Modal: ({ isOpen, portalContainerRef, onClose, aside, children, modalClassName, closeIconClassName, showCloseIcon, isDismissable, }: PropsWithChildren<ModalProps>) => import("react/jsx-runtime").JSX.Element;
@@ -4,7 +4,7 @@ import { ModalOverlay, Modal as Modal$1, Dialog } from "react-aria-components";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import { CloseIcon } from "../../../assets/icons/Close.js";
6
6
  import { InlineIconButton } from "../../buttons/InlineIconButton/InlineIconButton.js";
7
- import { modalOverlay, modalContent, modalMain } from "./modal.cva.js";
7
+ import { modalOverlay, modalMain, modalContent } from "./modal.cva.js";
8
8
  import { ns } from "../../../config/i18n.js";
9
9
  const Modal = ({
10
10
  isOpen,
@@ -13,6 +13,7 @@ const Modal = ({
13
13
  aside,
14
14
  children,
15
15
  modalClassName,
16
+ closeIconClassName,
16
17
  showCloseIcon,
17
18
  isDismissable = true
18
19
  }) => {
@@ -32,14 +33,17 @@ const Modal = ({
32
33
  onClose();
33
34
  }
34
35
  },
35
- children: /* @__PURE__ */ jsx(Modal$1, { className: clsx(modalMain({ aside })), children: /* @__PURE__ */ jsxs(Dialog, { className: modalContent({ aside, className: modalClassName }), children: [
36
+ children: /* @__PURE__ */ jsx(Modal$1, { className: modalMain({ aside }), children: /* @__PURE__ */ jsxs(Dialog, { className: modalContent({ aside, className: modalClassName }), children: [
36
37
  showCloseIcon && /* @__PURE__ */ jsx(
37
38
  InlineIconButton,
38
39
  {
39
40
  color: "secondary",
40
41
  onPress: onClose,
41
42
  icon: CloseIcon,
42
- className: "absolute top-0 right-0 p-2",
43
+ className: clsx(
44
+ "absolute top-0 right-0 p-modal-gap-desktop-modal-close-icon-top-right",
45
+ closeIconClassName
46
+ ),
43
47
  label: t(($) => $.ui.modal.closeBtn)
44
48
  }
45
49
  ),
@@ -5,7 +5,7 @@ const tooltipCva = cva(
5
5
  variants: {
6
6
  color: {
7
7
  default: ["bg-interactive-contained-secondary-idle"],
8
- error: ["w-max bg-text-error-1"]
8
+ error: ["w-max bg-elevation-fill-error-1"]
9
9
  }
10
10
  },
11
11
  defaultVariants: {
@@ -19,7 +19,7 @@ const tooltipPointerHorizontalCva = cva(
19
19
  variants: {
20
20
  color: {
21
21
  default: ["text-interactive-contained-secondary-idle"],
22
- error: ["text-text-error-1"]
22
+ error: ["text-elevation-fill-error-1"]
23
23
  }
24
24
  },
25
25
  defaultVariants: {
@@ -33,7 +33,7 @@ const tooltipPointerVerticalCva = cva(
33
33
  variants: {
34
34
  color: {
35
35
  default: ["text-interactive-contained-secondary-idle"],
36
- error: ["text-text-error-1"]
36
+ error: ["text-elevation-fill-error-1"]
37
37
  }
38
38
  },
39
39
  defaultVariants: {
@@ -45,7 +45,7 @@ const tooltipTextCva = cva([], {
45
45
  variants: {
46
46
  color: {
47
47
  default: ["text-text-inverted-3"],
48
- error: ["text-interactive-text-secondary-idle-inverted"]
48
+ error: ["text-text-error-inverted-1"]
49
49
  }
50
50
  },
51
51
  defaultVariants: {
@@ -11,16 +11,17 @@ const SegmentItem = ({ label, icon, id, isDisabled, ...props }) => {
11
11
  ...props,
12
12
  id,
13
13
  className: clsx(
14
- "flex items-center justify-center gap-1 transition-all duration-200",
15
- "border border-transparent",
14
+ "flex items-center justify-center gap-segmented-control-gap-content-icon-to-text transition-all duration-200",
15
+ "border border-support-transparent-outline",
16
16
  "py-segmented-control-height-segment",
17
- iconOnly ? "px-segmented-control-height-segment" : "px-segmented-control-side-segment py-segmented-control-height-segment",
17
+ iconOnly ? "px-segmented-control-height-segment" : "px-segmented-control-side-segment",
18
18
  "rounded-segmented-control-rounding-segment",
19
- "hover:bg-interactive-subtle-secondary-hover",
20
- "pressed:bg-interactive-subtle-secondary-pressed",
21
- "selected:text-interactive-subtle-secondary-on-toggled-idle text-interactive-subtle-secondary-on-idle hover:text-interactive-subtle-secondary-on-hover",
22
- "disabled:selected:text-interactive-subtle-secondary-on-toggled-disabled disabled:text-interactive-subtle-secondary-on-disabled",
23
- "selected:bg-interactive-subtle-secondary-toggled-idle disabled:selected:bg-interactive-subtle-secondary-toggled-disabled"
19
+ "text-interactive-subtle-secondary-on-idle",
20
+ "hover:border-interactive-subtle-secondary-hover hover:bg-interactive-subtle-secondary-hover hover:text-interactive-subtle-secondary-on-hover",
21
+ "pressed:border-interactive-subtle-secondary-pressed pressed:bg-interactive-subtle-secondary-pressed pressed:text-interactive-subtle-secondary-on-pressed",
22
+ "disabled:border-support-transparent-outline disabled:text-interactive-subtle-secondary-on-disabled",
23
+ "selected:border-interactive-subtle-secondary-toggled-idle selected:bg-interactive-subtle-secondary-toggled-idle selected:text-interactive-subtle-secondary-on-toggled-idle",
24
+ "selected:disabled:border-interactive-subtle-secondary-toggled-disabled selected:disabled:bg-interactive-subtle-secondary-toggled-disabled selected:disabled:text-interactive-subtle-secondary-on-toggled-disabled"
24
25
  ),
25
26
  isDisabled,
26
27
  children: [
@@ -6,7 +6,7 @@ export interface PaginationListProps {
6
6
  hideFirstLast?: boolean;
7
7
  hidePrevNext?: boolean;
8
8
  hideNumbers?: boolean;
9
- size?: ButtonProps["size"];
9
+ size?: Exclude<ButtonProps["size"], "none">;
10
10
  color?: ButtonProps["color"];
11
11
  variant?: ButtonProps["variant"];
12
12
  className?: string;
@@ -1,3 +1,3 @@
1
1
  export declare const minWidth: (props?: ({
2
- size?: "xxs" | "xs" | "s" | "m" | "l" | null | undefined;
2
+ size?: "xs" | "s" | "m" | "l" | "xxs" | null | undefined;
3
3
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
@@ -32,7 +32,8 @@ const Toast = ({ text, isLoading = false, actions = [], icon: Icon, ...props })
32
32
  onPress,
33
33
  ...buttonColor({ ...props }),
34
34
  variant: "text",
35
- size: "xs",
35
+ size: "none",
36
+ width: "hug",
36
37
  children: buttonText
37
38
  },
38
39
  buttonText
@@ -1,7 +1,11 @@
1
+ import { z } from 'zod';
2
+ type FilterValue = string | string[] | boolean | number;
1
3
  export interface FilterStore<TFilterData> {
2
4
  filterData: TFilterData;
3
5
  setFilterValue: (data: Partial<TFilterData>) => void;
4
6
  getFilterValue: (keys: (keyof TFilterData)[]) => Partial<TFilterData>;
5
7
  clearAllFilters: () => void;
6
8
  }
7
- export declare const useFilters: <TFilterData>(defaultFilterValues?: TFilterData, prefix?: string) => FilterStore<TFilterData>;
9
+ export declare function parseFilterFromQuery(query: Record<string, any>, schema?: z.ZodObject<any>): Record<string, FilterValue>;
10
+ export declare function useFilters<TFilterData>(defaultFilterValues?: TFilterData, prefix?: string, schema?: z.ZodObject<any>): FilterStore<TFilterData>;
11
+ export {};
@@ -1,107 +1,160 @@
1
- import { useState, useEffect } from "react";
2
- import { UIRouter } from "../config/router.context.js";
3
- const serializeFiltersToQuery = (filterData, prefix) => {
1
+ import { useNavigate, useLocation } from "@tanstack/react-router";
2
+ import { useMemo, useRef, useState, useEffect } from "react";
3
+ import { z } from "zod";
4
+ function getFieldType(schema, fieldKey) {
5
+ if (!schema || !schema.shape) return "unknown";
6
+ let fieldSchema = schema.shape[fieldKey];
7
+ while (fieldSchema) {
8
+ if (fieldSchema instanceof z.ZodOptional || fieldSchema instanceof z.ZodNullable) {
9
+ fieldSchema = fieldSchema.unwrap();
10
+ } else {
11
+ break;
12
+ }
13
+ }
14
+ if (!fieldSchema) return "unknown";
15
+ if (fieldSchema instanceof z.ZodNumber) return "number";
16
+ if (fieldSchema instanceof z.ZodBoolean) return "boolean";
17
+ if (fieldSchema instanceof z.ZodString) return "string";
18
+ return "unknown";
19
+ }
20
+ function serializeFiltersToQuery(filterData, prefix) {
4
21
  const query = {};
5
22
  for (const [key, value] of Object.entries(filterData)) {
6
23
  if (value === null || value === void 0) {
7
24
  continue;
8
25
  }
9
- const filterKey = `filter[${prefix && `${prefix}-`}${key}]`;
10
26
  if (Array.isArray(value) || typeof value === "object") {
11
- query[filterKey] = JSON.stringify(value);
27
+ query[`filter[${prefix && `${prefix}-`}${key}]`] = JSON.stringify(value);
12
28
  } else if (typeof value === "boolean") {
13
- query[filterKey] = value ? "true" : "false";
29
+ query[`filter[${prefix && `${prefix}-`}${key}]`] = value ? "true" : "false";
30
+ } else if (typeof value === "number") {
31
+ query[`filter[${prefix && `${prefix}-`}${key}]`] = value.toString();
14
32
  } else {
15
- query[filterKey] = value;
33
+ query[`filter[${prefix && `${prefix}-`}${key}]`] = value;
16
34
  }
17
35
  }
18
36
  return query;
19
- };
20
- const parseFilterFromQuery = (query) => {
37
+ }
38
+ function parseFilterFromQuery(query, schema) {
21
39
  const filter = {};
22
40
  for (const [key, value] of Object.entries(query)) {
23
- const match = /^filter\[(.+?)\]$/.exec(key);
24
- if (!match) {
25
- continue;
26
- }
27
- const filterKey = match[1];
28
- const isArray = value.startsWith("[") && value.endsWith("]");
29
- const isObject = value.startsWith("{") && value.endsWith("}");
30
- const isNumber = !Number.isNaN(Number(value)) && value !== "";
31
- const isBoolean = ["true", "false"].includes(value);
32
- if (isArray || isObject || isNumber) {
33
- filter[filterKey] = JSON.parse(value);
34
- } else if (isBoolean) {
35
- filter[filterKey] = value === "true";
36
- } else {
37
- filter[filterKey] = value;
41
+ const match = key.match(/^filter\[(.+?)\]$/);
42
+ if (match) {
43
+ const filterKey = match[1];
44
+ const baseKey = filterKey.includes("-") ? filterKey.split("-").pop() || filterKey : filterKey;
45
+ if (value.startsWith("[") && value.endsWith("]") || value.startsWith("{") && value.endsWith("}")) {
46
+ filter[filterKey] = JSON.parse(value);
47
+ } else if (["true", "false"].includes(value)) {
48
+ filter[filterKey] = value === "true";
49
+ } else {
50
+ const expectedType = getFieldType(schema, baseKey);
51
+ if (expectedType === "number" && !Number.isNaN(Number(value)) && value !== "" && value !== null) {
52
+ filter[filterKey] = Number(value);
53
+ } else if (expectedType === "boolean" && ["true", "false"].includes(value)) {
54
+ filter[filterKey] = value === "true";
55
+ } else {
56
+ filter[filterKey] = value;
57
+ }
58
+ }
38
59
  }
39
60
  }
40
61
  return filter;
41
- };
42
- const useFilters = (defaultFilterValues, prefix = "") => {
43
- const { query, pathname, replace } = UIRouter.useUIRouter();
44
- const queryFilters = parseFilterFromQuery(query);
62
+ }
63
+ function useFilters(defaultFilterValues, prefix = "", schema) {
64
+ const navigate = useNavigate();
65
+ const location = useLocation();
66
+ const queryObject = useMemo(
67
+ () => Object.fromEntries(new URLSearchParams(location.searchStr || "").entries()),
68
+ [location.searchStr]
69
+ );
70
+ const hasAppliedDefaultsRef = useRef(false);
71
+ const lastPathnameRef = useRef(location.pathname);
72
+ const queryFilters = parseFilterFromQuery(queryObject, schema);
45
73
  const [filterData, setFilterData] = useState(queryFilters);
46
74
  useEffect(() => {
47
- const setUrlToDefaultFilters = () => {
75
+ if (lastPathnameRef.current !== location.pathname) {
76
+ hasAppliedDefaultsRef.current = false;
77
+ lastPathnameRef.current = location.pathname;
78
+ }
79
+ }, [location.pathname]);
80
+ useEffect(() => {
81
+ const currentQueryObject = Object.fromEntries(new URLSearchParams(location.searchStr || "").entries());
82
+ const currentFilters = parseFilterFromQuery(currentQueryObject, schema);
83
+ const shouldApplyDefaults = defaultFilterValues && Object.keys(currentFilters).length === 0 && !hasAppliedDefaultsRef.current;
84
+ if (shouldApplyDefaults) {
48
85
  const flatFilterQuery = serializeFiltersToQuery(defaultFilterValues, prefix);
49
- replace({
50
- pathname,
51
- query: {
52
- ...query,
53
- ...flatFilterQuery
86
+ const sp = new URLSearchParams(location.searchStr || "");
87
+ let needsNavigation = false;
88
+ Object.entries(flatFilterQuery).forEach(([k, v]) => {
89
+ const currentValue = sp.get(k);
90
+ if (Array.isArray(v)) {
91
+ const currentValues = sp.getAll(k);
92
+ if (JSON.stringify(currentValues.sort()) !== JSON.stringify(v.sort())) {
93
+ needsNavigation = true;
94
+ }
95
+ } else {
96
+ if (currentValue !== v) {
97
+ needsNavigation = true;
98
+ }
54
99
  }
55
100
  });
56
- };
57
- const currentFilters = parseFilterFromQuery(query);
58
- const hasFiltersSet = Object.keys(currentFilters).length > 0;
59
- if (defaultFilterValues && !hasFiltersSet) {
60
- setUrlToDefaultFilters();
101
+ if (needsNavigation) {
102
+ Object.entries(flatFilterQuery).forEach(([k, v]) => {
103
+ sp.delete(k);
104
+ if (Array.isArray(v)) {
105
+ v.forEach((val) => sp.append(k, val));
106
+ } else {
107
+ sp.set(k, v);
108
+ }
109
+ });
110
+ const to = `${location.pathname}${sp.toString() ? `?${sp.toString()}` : ""}`;
111
+ navigate({ to, replace: true });
112
+ }
113
+ hasAppliedDefaultsRef.current = true;
61
114
  }
62
- }, [defaultFilterValues, prefix]);
115
+ }, [defaultFilterValues, prefix, location.pathname, location.searchStr]);
63
116
  useEffect(() => {
64
- const newQueryFilters = parseFilterFromQuery(query);
117
+ const newQueryFilters = parseFilterFromQuery(queryObject, schema);
65
118
  setFilterData(newQueryFilters);
66
- }, [query]);
119
+ }, [queryObject, schema]);
67
120
  const setFilterValue = (data) => {
68
- let newFilters = { ...filterData };
121
+ const next = { ...filterData };
69
122
  const isReset = Object.keys(data).length === 0;
70
123
  const isResetToDefault = data === defaultFilterValues;
71
124
  if (isResetToDefault) {
72
- newFilters = {
73
- ...defaultFilterValues
74
- };
75
- } else if (isReset) {
76
- newFilters = {};
77
- } else {
125
+ Object.keys(next).forEach((key) => delete next[key]);
126
+ Object.entries(defaultFilterValues ?? {}).forEach(([key, value]) => {
127
+ next[key] = value;
128
+ });
129
+ } else if (!isReset) {
78
130
  Object.entries(data).forEach(([key, value]) => {
79
131
  if (value === void 0) {
80
- delete newFilters[key];
132
+ delete next[key];
81
133
  } else {
82
- newFilters[key] = value;
134
+ next[key] = value;
83
135
  }
84
136
  });
137
+ } else {
138
+ Object.keys(next).forEach((key) => delete next[key]);
85
139
  }
86
- const prefixPart = prefix ? `${prefix}-` : "";
87
- const cleanedQuery = Object.keys(query).reduce(
88
- (acc, k) => {
89
- if (!k.startsWith(`filter[${prefixPart}`)) {
90
- acc[k] = query[k];
91
- }
92
- return acc;
93
- },
94
- {}
95
- );
96
- const flatFilterQuery = serializeFiltersToQuery(newFilters, prefix);
97
- replace({
98
- pathname,
99
- query: {
100
- ...cleanedQuery,
101
- ...flatFilterQuery
140
+ const sp = new URLSearchParams(location.searchStr || "");
141
+ Array.from(sp.keys()).forEach((key) => {
142
+ if (key.startsWith(`filter[${prefix && `${prefix}-`}`)) {
143
+ sp.delete(key);
102
144
  }
103
145
  });
104
- setFilterData(newFilters);
146
+ const flatFilterQuery = serializeFiltersToQuery(next, prefix);
147
+ Object.entries(flatFilterQuery).forEach(([k, v]) => {
148
+ sp.delete(k);
149
+ if (Array.isArray(v)) {
150
+ v.forEach((val) => sp.append(k, val));
151
+ } else {
152
+ sp.set(k, v);
153
+ }
154
+ });
155
+ const to = `${location.pathname}${sp.toString() ? `?${sp.toString()}` : ""}`;
156
+ navigate({ to, replace: true });
157
+ setFilterData(next);
105
158
  };
106
159
  const getFilterValue = (keys) => {
107
160
  const result = {};
@@ -114,7 +167,8 @@ const useFilters = (defaultFilterValues, prefix = "") => {
114
167
  setFilterValue(defaultFilterValues ?? {});
115
168
  };
116
169
  return { filterData, setFilterValue, getFilterValue, clearAllFilters };
117
- };
170
+ }
118
171
  export {
172
+ parseFilterFromQuery,
119
173
  useFilters
120
174
  };
package/dist/index.d.ts CHANGED
@@ -69,6 +69,7 @@ export type { FileUploadContainerProps } from './components/inputs/File/FileUplo
69
69
  export { FileUploadContainer } from './components/inputs/File/FileUploadContainer';
70
70
  export type { InputUploadProps } from './components/inputs/File/InputUpload';
71
71
  export { InputUpload } from './components/inputs/File/InputUpload';
72
+ export { ProgressBar } from './components/inputs/File/shared/ProgressBar';
72
73
  export type { FormFieldProps } from './components/inputs/FormField/FormField';
73
74
  export { FormField } from './components/inputs/FormField/FormField';
74
75
  export type { ControlledNumberInputProps, NumberInputProps } from './components/inputs/Input/NumberInput/NumberInput';
package/dist/index.js CHANGED
@@ -51,6 +51,7 @@ import { TimePicker } from "./components/inputs/DateTime/TimePicker/TimePicker.j
51
51
  import { FileUpload } from "./components/inputs/File/FileUpload.js";
52
52
  import { FileUploadContainer } from "./components/inputs/File/FileUploadContainer.js";
53
53
  import { InputUpload } from "./components/inputs/File/InputUpload.js";
54
+ import { ProgressBar } from "./components/inputs/File/shared/ProgressBar.js";
54
55
  import { FormField } from "./components/inputs/FormField/FormField.js";
55
56
  import { NumberInput } from "./components/inputs/Input/NumberInput/NumberInput.js";
56
57
  import { PasswordInput } from "./components/inputs/Input/PasswordInput/PasswordInput.js";
@@ -216,6 +217,7 @@ export {
216
217
  PillButton,
217
218
  PointerHorizontalIcon,
218
219
  PointerVerticalIcon,
220
+ ProgressBar,
219
221
  QueriesUtils,
220
222
  QueryAutocomplete,
221
223
  RadioGroup,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@povio/ui",
3
- "version": "0.1.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,3 +0,0 @@
1
- export declare const FileProgressBar: ({ progress }: {
2
- progress?: number;
3
- }) => import("react/jsx-runtime").JSX.Element;
@@ -1,32 +0,0 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import { Typography } from "../../../text/Typography/Typography.js";
3
- const FileProgressBar = ({ progress = 0 }) => {
4
- return /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-center gap-file-upload-content-gap-progress-actions", children: [
5
- /* @__PURE__ */ jsx("div", { className: "flex flex-fill flex-col items-start gap-2 py-progress-height-height", children: /* @__PURE__ */ jsxs("div", { className: "relative h-1 w-full", children: [
6
- /* @__PURE__ */ jsx("div", { className: "h-1 w-full flex-shrink-0 rounded-xs bg-input-filled-idle" }),
7
- /* @__PURE__ */ jsx(
8
- "div",
9
- {
10
- className: "absolute top-0 left-0 h-1 flex-shrink-0 rounded-xs bg-interactive-contained-primary-idle transition-all duration-300",
11
- style: { width: `${progress}%` }
12
- }
13
- )
14
- ] }) }),
15
- /* @__PURE__ */ jsxs(
16
- Typography,
17
- {
18
- variant: "default",
19
- size: "label-2",
20
- as: "span",
21
- className: "text-center text-text-default-1",
22
- children: [
23
- progress,
24
- "%"
25
- ]
26
- }
27
- )
28
- ] });
29
- };
30
- export {
31
- FileProgressBar
32
- };