@sqrzro/ui 4.0.0-alpha.56 → 4.0.0-alpha.58

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.
@@ -19,6 +19,7 @@ export interface ButtonProps extends ClassNameProps<ButtonClassNames> {
19
19
  readonly id?: string;
20
20
  /** If true, the Button will be greyed out and non-interactive */
21
21
  readonly isDisabled?: boolean;
22
+ readonly isDownload?: boolean;
22
23
  /** If true, the button will stretch to the full width of its container */
23
24
  readonly isFullWidth?: boolean;
24
25
  /** If true, the Button will show a loading indicator, and be non-interactive */
@@ -61,5 +62,5 @@ export interface ButtonProps extends ClassNameProps<ButtonClassNames> {
61
62
  * | ---- | ----------- | ---- |
62
63
  * | root | The button element | `StylableButtonClassName` |
63
64
  */
64
- declare function Button({ children, classNames, classNameProps, form, hasAssistiveLabel, href, icon, id, isDisabled, isFullWidth, isLoading, isNewWindow, isText, name, onClick, popoverTarget, scroll, style, type, value, variant, }: ButtonProps): React.ReactElement;
65
+ declare function Button({ children, classNames, classNameProps, form, hasAssistiveLabel, href, icon, id, isDisabled, isDownload, isFullWidth, isLoading, isNewWindow, isText, name, onClick, popoverTarget, scroll, style, type, value, variant, }: ButtonProps): React.ReactElement;
65
66
  export default Button;
@@ -24,7 +24,7 @@ import Link from '../../elements/Link';
24
24
  * | ---- | ----------- | ---- |
25
25
  * | root | The button element | `StylableButtonClassName` |
26
26
  */
27
- function Button({ children, classNames, classNameProps, form, hasAssistiveLabel, href, icon, id, isDisabled, isFullWidth, isLoading, isNewWindow, isText, name, onClick, popoverTarget, scroll, style, type, value, variant, }) {
27
+ function Button({ children, classNames, classNameProps, form, hasAssistiveLabel, href, icon, id, isDisabled, isDownload, isFullWidth, isLoading, isNewWindow, isText, name, onClick, popoverTarget, scroll, style, type, value, variant, }) {
28
28
  const ref = useRef(null);
29
29
  const componentClassNames = useClassNames(isText ? 'textButton' : 'button', { props: { ...classNameProps, ...applyVariants(variant) } }, classNames);
30
30
  const className = tw('relative flex cursor-pointer items-center justify-center whitespace-nowrap', isDisabled || isLoading ? 'pointer-events-none opacity-30' : null, isFullWidth ? 'w-full' : null, componentClassNames?.root);
@@ -33,7 +33,7 @@ function Button({ children, classNames, classNameProps, form, hasAssistiveLabel,
33
33
  * headless in other applications, and so should have consistency.
34
34
  */
35
35
  if (href) {
36
- return (_jsx(Link, { className: className, href: href, id: id, isNewWindow: isNewWindow, onClick: onClick, scroll: scroll, style: style, children: _jsxs("span", { className: "flex items-center gap-2", children: [icon ? (_jsx("span", { className: tw('flex-none', componentClassNames?.icon), children: icon })) : null, _jsx("span", { className: tw(hasAssistiveLabel ? 'sr-only' : null), children: children })] }) }));
36
+ return (_jsx(Link, { className: className, href: href, id: id, isDownload: isDownload, isNewWindow: isNewWindow, onClick: onClick, scroll: scroll, style: style, children: _jsxs("span", { className: "flex items-center gap-2", children: [icon ? (_jsx("span", { className: tw('flex-none', componentClassNames?.icon), children: icon })) : null, _jsx("span", { className: tw(hasAssistiveLabel ? 'sr-only' : null), children: children })] }) }));
37
37
  }
38
38
  return (_jsxs("button", { ref: ref, className: className, disabled: isDisabled || isLoading || false, form: form, id: id, name: name, onClick: onClick, popoverTarget: popoverTarget, style: style, type: type === 'submit' ? 'submit' : 'button', value: value, children: [isLoading ? (_jsx("div", { className: "absolute left-1/2 top-1/2 -translate-x-2/4 -translate-y-2/4", children: "\u2022\u2022\u2022" })) : null, _jsxs("span", { className: "flex items-center gap-2", children: [icon ? (_jsx("span", { className: tw('flex-none', componentClassNames?.icon), children: icon })) : null, _jsx("span", { className: tw(hasAssistiveLabel ? 'sr-only' : null, isLoading ? 'text-transparent' : null), children: children })] })] }));
39
39
  }
@@ -1,19 +1,9 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { isValidElement } from 'react';
4
3
  import { filterNull } from '@sqrzro/utility';
5
4
  import { useClassNames } from '../../../styles/context';
6
5
  import isDataTableArray from '../../../utility/is-data-object-array';
7
6
  import DataTable from '../DataTable';
8
- function getKey(item) {
9
- if (isValidElement(item)) {
10
- return item.key || '-';
11
- }
12
- if (typeof item === 'object') {
13
- return JSON.stringify(item);
14
- }
15
- return String(item);
16
- }
17
7
  function ListItemMeta({ classNameProps, classNames, data, }) {
18
8
  const componentClassNames = useClassNames('listItemMeta', { props: classNameProps }, classNames);
19
9
  if (!data?.length) {
@@ -23,6 +13,6 @@ function ListItemMeta({ classNameProps, classNames, data, }) {
23
13
  if (isDataTableArray(filteredData)) {
24
14
  return _jsx(DataTable, { data: filteredData });
25
15
  }
26
- return (_jsx("ul", { className: componentClassNames?.root, children: filteredData.map((item) => (_jsx("li", { className: "whitespace-nowrap", children: item }, getKey(item)))) }));
16
+ return (_jsx("ul", { className: componentClassNames?.root, children: filteredData.map((item, index) => (_jsx("li", { children: item }, index))) }));
27
17
  }
28
18
  export default ListItemMeta;
@@ -1,15 +1,19 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { isValidElement } from 'react';
4
3
  import { useClassNames } from '../../../styles/context';
5
4
  import ListItemMeta from '../ListItemMeta';
5
+ function isReactElement(element) {
6
+ return typeof element === 'object' && element !== null && '$$typeof' in element;
7
+ }
6
8
  function ListItemSecondary({ classNameProps, classNames, data, }) {
7
9
  const componentClassNames = useClassNames('listItem', { props: classNameProps }, classNames);
8
10
  if (!data) {
9
11
  return null;
10
12
  }
11
- if (isValidElement(data)) {
12
- return _jsx("div", { className: componentClassNames?.secondary, children: data });
13
+ // We have to use a custom check rather than the React.isValidElement type guard, because
14
+ // (seemingly as of Next 16.2) we get a JSON circular error using it.
15
+ if (isReactElement(data)) {
16
+ return data;
13
17
  }
14
18
  return (_jsxs("div", { className: componentClassNames?.secondary, children: [data.title ? _jsx("p", { className: componentClassNames?.title, children: data.title }) : null, data.description ? (_jsx("div", { className: componentClassNames?.description, children: data.description })) : null, _jsx(ListItemMeta, { classNameProps: { ...classNameProps, isSecondary: true }, data: data.meta })] }));
15
19
  }
@@ -10,10 +10,11 @@ export interface LinkProps extends ClassNameProps<LinkClassNames>, DataProps {
10
10
  children: React.ReactNode;
11
11
  href?: string | null;
12
12
  id?: string;
13
+ isDownload?: boolean;
13
14
  isNewWindow?: boolean;
14
15
  onClick?: React.MouseEventHandler<HTMLAnchorElement>;
15
16
  scroll?: boolean;
16
17
  style?: React.CSSProperties;
17
18
  }
18
- declare function Link({ children, className, classNames, classNameProps, isNewWindow, href, id, onClick, scroll, style, ...dataProps }: Readonly<LinkProps>): React.ReactElement;
19
+ declare function Link({ children, className, classNames, classNameProps, isDownload, isNewWindow, href, id, onClick, scroll, style, ...dataProps }: Readonly<LinkProps>): React.ReactElement;
19
20
  export default Link;
@@ -10,14 +10,16 @@ function normalizeHref(href) {
10
10
  }
11
11
  return joinURL(href);
12
12
  }
13
- function Link({ children, className, classNames, classNameProps, isNewWindow, href = '/', id, onClick, scroll, style, ...dataProps }) {
13
+ function Link({ children, className, classNames, classNameProps, isDownload, isNewWindow, href = '/', id, onClick, scroll, style, ...dataProps }) {
14
14
  const componentClassNames = useClassNames('link', { props: classNameProps }, classNames);
15
+ const downloadProps = isDownload ? { download: true } : {};
15
16
  const newWindowProps = isNewWindow
16
17
  ? { rel: 'noopener noreferrer', target: '_blank' }
17
18
  : {};
18
19
  const props = {
19
- ...newWindowProps,
20
20
  ...dataProps,
21
+ ...downloadProps,
22
+ ...newWindowProps,
21
23
  className: tw(componentClassNames?.root, className),
22
24
  id,
23
25
  onClick,
@@ -2,6 +2,8 @@ import type { ClassNameProps } from '../../../styles/classnames/interfaces';
2
2
  import type { ActionComponentProps } from '../../utility/Action';
3
3
  import type { MenuClassNames } from '../Menu';
4
4
  interface MenuItemProps<Data, Response> extends ClassNameProps<MenuClassNames>, ActionComponentProps<Data, Response> {
5
+ isDownload?: boolean;
6
+ isNewWindow?: boolean;
5
7
  }
6
- declare function MenuItem<Data, Response>({ children, classNameProps, classNames, href, isLoading, onClick, }: MenuItemProps<Data, Response>): React.ReactElement;
8
+ declare function MenuItem<Data, Response>({ children, classNameProps, classNames, href, isDownload, isLoading, isNewWindow, onClick, }: MenuItemProps<Data, Response>): React.ReactElement;
7
9
  export default MenuItem;
@@ -2,10 +2,10 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useClassNames } from '../../../styles/context';
4
4
  import Link from '../Link';
5
- function MenuItem({ children, classNameProps, classNames, href, isLoading, onClick, }) {
5
+ function MenuItem({ children, classNameProps, classNames, href, isDownload, isLoading, isNewWindow, onClick, }) {
6
6
  const componentClassNames = useClassNames('menu', { props: classNameProps }, classNames);
7
7
  if (href) {
8
- return (_jsx(Link, { className: componentClassNames?.link, href: href, onClick: onClick, children: children }));
8
+ return (_jsx(Link, { className: componentClassNames?.link, href: href, isDownload: isDownload, isNewWindow: isNewWindow, onClick: onClick, children: children }));
9
9
  }
10
10
  return (_jsxs("button", { className: componentClassNames?.link, onClick: onClick, type: "button", children: [children, " ", isLoading && '...'] }));
11
11
  }
@@ -20,7 +20,8 @@ export interface FormFieldProps<T, V extends T> extends ClassNameProps<FormField
20
20
  isOptional?: boolean;
21
21
  label?: React.ReactNode;
22
22
  onChange?: (event: InputEvent<T>) => void;
23
+ onFieldChange?: (event: InputEvent<T>) => void;
23
24
  render: (props: InputProps<T>) => React.ReactElement | null;
24
25
  }
25
- declare function FormField<T, V extends T>({ action, classNameProps, classNames, details, error, hasAssistiveError, hasAssistiveDetails, hasAssistiveLabel, id, isContentOnly, isDisabled, isOptional, label, name, onChange, onKeyDown, render, value, }: Readonly<FormFieldProps<T, V>>): React.ReactElement | null;
26
+ declare function FormField<T, V extends T>({ action, classNameProps, classNames, details, error, hasAssistiveError, hasAssistiveDetails, hasAssistiveLabel, id, isContentOnly, isDisabled, isOptional, label, name, onChange, onFieldChange, onKeyDown, render, value, }: Readonly<FormFieldProps<T, V>>): React.ReactElement | null;
26
27
  export default FormField;
@@ -11,10 +11,14 @@ function checkHasError(error) {
11
11
  Object.keys(error).length > 0 &&
12
12
  Object.values(error).some((item) => typeof item === 'string'));
13
13
  }
14
- function FormField({ action, classNameProps, classNames, details, error, hasAssistiveError, hasAssistiveDetails, hasAssistiveLabel, id, isContentOnly, isDisabled, isOptional, label, name, onChange, onKeyDown, render, value, }) {
14
+ function FormField({ action, classNameProps, classNames, details, error, hasAssistiveError, hasAssistiveDetails, hasAssistiveLabel, id, isContentOnly, isDisabled, isOptional, label, name, onChange, onFieldChange, onKeyDown, render, value, }) {
15
15
  const componentClassNames = useClassNames('formField', { props: classNameProps, states: { isError: checkHasError(error) } }, classNames);
16
16
  const inputId = id || `ff_${name}`;
17
17
  const [inputError, setInputError] = useState(null);
18
+ function handleChange(event) {
19
+ onFieldChange?.(event);
20
+ onChange?.(event);
21
+ }
18
22
  function handleError(message) {
19
23
  setInputError(message);
20
24
  }
@@ -28,7 +32,7 @@ function FormField({ action, classNameProps, classNames, details, error, hasAssi
28
32
  isOptional,
29
33
  label,
30
34
  name,
31
- onChange,
35
+ onChange: handleChange,
32
36
  onError: handleError,
33
37
  onKeyDown,
34
38
  value,
@@ -12,7 +12,7 @@ export interface FieldPropsReturn<Request, K extends keyof Request = keyof Reque
12
12
  isDisabled?: boolean;
13
13
  label?: string;
14
14
  name: string;
15
- onChange: (event: InputEvent<Request[K]>) => void;
15
+ onFieldChange: (event: InputEvent<Request[K]>) => void;
16
16
  value: Request[K];
17
17
  }
18
18
  export interface ToastsArgs {
@@ -173,7 +173,7 @@ function useForm({ defaults = {}, onError, onSubmit, onSuccess, onValidation, re
173
173
  error: getErrorsForField(errors, name),
174
174
  label: getLabel(name, label),
175
175
  name: name,
176
- onChange: handleChange,
176
+ onFieldChange: handleChange,
177
177
  value: data[name],
178
178
  };
179
179
  }
@@ -13,6 +13,7 @@ export function getFieldPropKeys() {
13
13
  'label',
14
14
  'name',
15
15
  'onChange',
16
+ 'onFieldChange',
16
17
  'onKeyDown',
17
18
  'value',
18
19
  ];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sqrzro/ui",
3
3
  "type": "module",
4
- "version": "4.0.0-alpha.56",
4
+ "version": "4.0.0-alpha.58",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "ISC",
@@ -52,18 +52,18 @@
52
52
  "dependencies": {
53
53
  "clsx": "^2.1.1",
54
54
  "drizzle-orm": "^0.45.1",
55
- "next": "^16.1.6",
55
+ "next": "^16.2.0",
56
56
  "react": "^19.2.4",
57
57
  "react-dom": "^19.2.4",
58
58
  "tailwind-merge": "^3.5.0",
59
59
  "use-deep-compare-effect": "^1.8.1",
60
- "@sqrzro/addons": "^4.0.0-alpha.9",
60
+ "@sqrzro/addons": "^4.0.0-alpha.10",
61
61
  "@sqrzro/utility": "^4.0.0-alpha.19"
62
62
  },
63
63
  "devDependencies": {
64
- "@storybook/addon-a11y": "^10.2.14",
65
- "@storybook/addon-docs": "^10.2.14",
66
- "@storybook/nextjs": "^10.2.14",
64
+ "@storybook/addon-a11y": "^10.3.1",
65
+ "@storybook/addon-docs": "^10.3.1",
66
+ "@storybook/nextjs": "^10.3.1",
67
67
  "@testing-library/react": "^16.3.2",
68
68
  "@testing-library/user-event": "^14.6.1",
69
69
  "@types/react": "^19.2.14",
@@ -72,7 +72,7 @@
72
72
  "cpx": "^1.5.0",
73
73
  "next-router-mock": "^1.0.5",
74
74
  "rimraf": "^6.1.3",
75
- "storybook": "^10.2.14",
75
+ "storybook": "^10.3.1",
76
76
  "tsc-alias": "^1.8.16",
77
77
  "tslib": "^2.8.1",
78
78
  "typescript": "^5.9.3"