@sqrzro/ui 4.0.0-alpha.21 → 4.0.0-alpha.22

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.
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { filterNull } from '@sqrzro/utility';
2
3
  import { getClassNames } from '../../../styles/classnames';
3
4
  import tw from '../../../styles/classnames/tw';
4
5
  import Link from '../../../components/utility/Link';
@@ -19,6 +20,7 @@ function ListItem({ $data, actions, classNames, classNameProps, description, hre
19
20
  const hasPrimary = Boolean(title || description || meta);
20
21
  const hasSecondary = Boolean(secondary);
21
22
  const hasTertiary = Boolean(tertiary);
22
- return (_jsx("li", { className: tw('@container relative grid', componentClassNames?.root, isLoading ? 'pointer-events-none animate-pulse' : null, actions ? 'grid-cols-[1fr_auto] gap-4' : 'grid-cols-1'), children: _jsxs("article", { className: "contents", children: [_jsxs("div", { className: tw('grid gap-x-6 gap-y-4', getLayout([hasPrimary, hasSecondary, hasTertiary])), children: [_jsxs("div", { className: componentClassNames?.primary, children: [title ? (_jsx("h2", { className: componentClassNames?.title, children: href ? (_jsx(Link, { className: "hover:text-link", href: href, children: title })) : (title) })) : null, description ? (_jsx("div", { className: componentClassNames?.description, children: description })) : null, _jsx(ListItemMeta, { classNameProps: classNameProps, data: meta })] }), tertiary ? (_jsx("div", { className: componentClassNames?.tertiary, children: tertiary })) : null, _jsx(ListItemSecondary, { classNameProps: classNameProps, classNames: classNames, data: secondary?.data, variant: variant })] }), actions ? _jsx(ListItemMenu, { actions: actions, data: $data }) : null] }) }));
23
+ const filteredActions = filterNull(actions || []);
24
+ return (_jsx("li", { className: tw('@container relative grid', componentClassNames?.root, isLoading ? 'pointer-events-none animate-pulse' : null, filteredActions.length ? 'grid-cols-[1fr_auto] gap-4' : 'grid-cols-1'), children: _jsxs("article", { className: "contents", children: [_jsxs("div", { className: tw('grid gap-x-6 gap-y-4', getLayout([hasPrimary, hasSecondary, hasTertiary])), children: [_jsxs("div", { className: componentClassNames?.primary, children: [title ? (_jsx("h2", { className: componentClassNames?.title, children: href ? (_jsx(Link, { className: "hover:text-link", href: href, children: title })) : (title) })) : null, description ? (_jsx("div", { className: componentClassNames?.description, children: description })) : null, _jsx(ListItemMeta, { classNameProps: classNameProps, data: meta })] }), tertiary ? (_jsx("div", { className: componentClassNames?.tertiary, children: tertiary })) : null, _jsx(ListItemSecondary, { classNameProps: classNameProps, classNames: classNames, data: secondary?.data, variant: variant })] }), filteredActions.length ? (_jsx(ListItemMenu, { actions: filteredActions, data: $data })) : null] }) }));
23
25
  }
24
26
  export default ListItem;
@@ -1,9 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { filterNull } from '@sqrzro/utility';
2
3
  import Switch from '../../../forms/components/Switch';
3
4
  import { getClassNames } from '../../../styles/classnames';
4
5
  import ListItemMenu from '../ListItemMenu';
5
6
  function TableRow({ classNameProps, classNames, columns, data, isSelected, onSelect, }) {
6
7
  const componentClassNames = getClassNames('table', { props: classNameProps, states: { isSelected } }, classNames);
7
- return (_jsxs("tr", { className: componentClassNames?.row, children: [data.isSelectable ? (_jsx("td", { children: _jsx(Switch, { name: data.id, onChange: onSelect, value: isSelected }) })) : null, columns.map((column) => (_jsx("td", { className: componentClassNames?.cell, children: data.row[column.id] }, column.id))), data.actions ? (_jsx("td", { children: _jsx(ListItemMenu, { actions: data.actions, data: data.$data }) })) : null] }));
8
+ const filteredActions = filterNull(data.actions || []);
9
+ return (_jsxs("tr", { className: componentClassNames?.row, children: [data.isSelectable ? (_jsx("td", { children: _jsx(Switch, { name: data.id, onChange: onSelect, value: isSelected }) })) : null, columns.map((column) => (_jsx("td", { className: componentClassNames?.cell, children: data.row[column.id] }, column.id))), filteredActions.length ? (_jsx("td", { children: _jsx(ListItemMenu, { actions: filteredActions, data: data.$data }) })) : null] }));
8
10
  }
9
11
  export default TableRow;
@@ -7,7 +7,7 @@ export interface CollectionFunctionConfig<Filters = null> {
7
7
  export type ListFunctionConfig<Filters = null> = CollectionFunctionConfig<Filters>;
8
8
  export interface ListItemObject<Data extends object | null = null> {
9
9
  $data?: Data;
10
- actions?: ActionObject<Data>[];
10
+ actions?: (ActionObject<Data> | null)[];
11
11
  description?: React.ReactNode;
12
12
  href?: string;
13
13
  id: string;
@@ -31,7 +31,7 @@ export interface ListItemObject<Data extends object | null = null> {
31
31
  export type TableFunctionConfig<Filters = null> = CollectionFunctionConfig<Filters>;
32
32
  export interface TableItemObject<Data extends object | null = null> {
33
33
  $data?: Data;
34
- actions?: ActionObject<Data>[];
34
+ actions?: (ActionObject<Data> | null)[];
35
35
  id: string;
36
36
  isSelectable?: boolean;
37
37
  row: {
@@ -4,6 +4,7 @@ import ActionButton from '../../../components/buttons/ActionButton';
4
4
  import { getClassNames } from '../../../styles/classnames';
5
5
  function ActionList({ actions, classNameProps, classNames, isLoading, }) {
6
6
  const componentClassNames = getClassNames('actionList', { props: classNameProps }, classNames);
7
- return (_jsx("ul", { className: componentClassNames?.root, children: filterNull(actions).map((action, index) => (_jsx("li", { className: componentClassNames?.item, children: _jsx(ActionButton, { ...action, isLoading: Boolean(isLoading || action.isLoading) }) }, index))) }));
7
+ const filteredActions = filterNull(actions);
8
+ return (_jsx("ul", { className: componentClassNames?.root, children: filteredActions.map((action, index) => (_jsx("li", { className: componentClassNames?.item, children: _jsx(ActionButton, { ...action, isLoading: Boolean(isLoading || action.isLoading) }) }, index))) }));
8
9
  }
9
10
  export default ActionList;
@@ -1,7 +1,7 @@
1
1
  import type { ActionObject } from '../../../utility/interfaces';
2
2
  import { PopoverProps } from '../Popover';
3
3
  export interface MenuProps<Data extends object | null = null> {
4
- actions: ActionObject<Data>[];
4
+ actions: (ActionObject<Data> | null)[];
5
5
  align?: PopoverProps['align'];
6
6
  data?: Data;
7
7
  label: React.ReactNode;
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { filterNull } from '@sqrzro/utility';
3
4
  import Button from '../../../components/buttons/Button';
4
5
  import useClickOutside from '../../../hooks/useClickOutside';
5
6
  import Action from '../Action';
@@ -10,6 +11,7 @@ function Menu({ actions, align, data, label, vAlign, }) {
10
11
  function toggleIsOpen() {
11
12
  setIsOpen(!isOpen);
12
13
  }
13
- return (_jsxs("div", { className: "relative", ref: ref, children: [_jsx(Button, { onClick: toggleIsOpen, children: label }), _jsx(Popover, { align: align, isOpen: isOpen, vAlign: vAlign, children: _jsx("ul", { children: actions.map((item, index) => (_jsx("li", { children: _jsx(Action, { ...item, data: data, render: MenuItem }) }, index))) }) })] }));
14
+ const filteredActions = filterNull(actions);
15
+ return (_jsxs("div", { className: "relative", ref: ref, children: [_jsx(Button, { onClick: toggleIsOpen, children: label }), _jsx(Popover, { align: align, isOpen: isOpen, vAlign: vAlign, children: _jsx("ul", { children: filteredActions.map((item, index) => (_jsx("li", { children: _jsx(Action, { ...item, data: data, render: MenuItem }) }, index))) }) })] }));
14
16
  }
15
17
  export default Menu;
@@ -0,0 +1,15 @@
1
+ import { InputProps } from '../../../forms/interfaces';
2
+ import type { ClassNameProps } from '../../../styles/classnames/interfaces';
3
+ export interface CSVInputClassNames {
4
+ root: string;
5
+ icon: string;
6
+ title: string;
7
+ description: string;
8
+ }
9
+ export interface CSVInputComponentProps {
10
+ icon?: React.ReactNode;
11
+ iconSuccess?: React.ReactNode;
12
+ }
13
+ export type CSVInputProps = ClassNameProps<CSVInputClassNames> & InputProps<string> & CSVInputComponentProps;
14
+ declare function CSVInput({ classNames, classNameProps, icon, iconSuccess, id, isDisabled, name, onChange, value, }: Readonly<CSVInputProps>): React.ReactElement;
15
+ export default CSVInput;
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { formatPlural } from '@sqrzro/utility';
5
+ import { getClassNames } from '../../../styles/classnames';
6
+ import tw from '../../../styles/classnames/tw';
7
+ import formatFileSize from '../../utility/format-file-size';
8
+ function CSVInput({ classNames, classNameProps, icon, iconSuccess, id, isDisabled, name, onChange, value, }) {
9
+ const componentClassNames = getClassNames('csvInput', { props: classNameProps }, classNames);
10
+ const [fileData, setFileData] = useState(null);
11
+ function handleChange(event) {
12
+ const { files } = event.target;
13
+ const reader = new FileReader();
14
+ reader.addEventListener('load', () => {
15
+ const text = reader.result?.toString() || '';
16
+ const lines = text.split('\n').filter((item) => item.trim() !== '').length;
17
+ setFileData({
18
+ lines,
19
+ name: files?.[0].name || '',
20
+ size: files?.[0].size || 0,
21
+ });
22
+ onChange?.({ target: { name, value: text } });
23
+ }, false);
24
+ if (files?.[0]) {
25
+ reader.readAsText(files[0]);
26
+ }
27
+ }
28
+ return (_jsxs("div", { className: tw('relative flex items-center justify-center', componentClassNames?.root), children: [_jsx("input", { name: name, type: "hidden", value: value || '' }), _jsx("input", { accept: "text/csv", className: "absolute left-0 top-0 h-full w-full cursor-pointer opacity-0", disabled: isDisabled, id: id || name, name: name, onChange: handleChange, type: "file" }), fileData ? (_jsxs("div", { className: tw('text-center', isDisabled ? 'pointer-events-none opacity-20' : null), children: [_jsx("i", { className: componentClassNames?.icon, children: iconSuccess }), _jsx("p", { className: componentClassNames?.title, children: fileData.name }), _jsxs("p", { className: componentClassNames?.description, children: [formatFileSize(fileData.size), " \u2022", ' ', formatPlural('line', fileData.lines)] })] })) : (_jsxs("div", { className: "text-center", children: [_jsx("i", { className: componentClassNames?.icon, children: icon }), _jsx("p", { className: componentClassNames?.title, children: "Click to Upload" }), _jsx("p", { className: componentClassNames?.description, children: "(or drag a CSV file over this box)" })] }))] }));
29
+ }
30
+ export default CSVInput;
@@ -1,5 +1,6 @@
1
1
  import type { FormFieldComponentProps } from '../../interfaces';
2
2
  import type { AutocompleteComponentProps } from '../Autocomplete';
3
+ import type { CSVInputComponentProps } from '../CSVInput';
3
4
  import type { DropdownComponentProps } from '../Dropdown';
4
5
  import type { PointsInputComponentProps } from '../PointsInput';
5
6
  import type { NumberInputComponentProps } from '../NumberInput';
@@ -7,6 +8,8 @@ import type { PasswordInputComponentProps } from '../PasswordInput';
7
8
  import type { TextInputComponentProps } from '../TextInput';
8
9
  export type AutocompleteFormFieldProps<T> = FormFieldComponentProps<T | null> & AutocompleteComponentProps<T>;
9
10
  export declare function AutocompleteFormField<T>(props: Readonly<AutocompleteFormFieldProps<T>>): React.ReactElement;
11
+ export type CSVFormFieldProps = FormFieldComponentProps<string> & CSVInputComponentProps;
12
+ export declare function CSVFormField(props: Readonly<CSVFormFieldProps>): React.ReactElement;
10
13
  export type DropdownFormFieldProps<T> = FormFieldComponentProps<T | null> & DropdownComponentProps<T>;
11
14
  export declare function DropdownFormField<T>(props: Readonly<DropdownFormFieldProps<T>>): React.ReactElement;
12
15
  export type MoneyFormFieldProps = FormFieldComponentProps<number> & PointsInputComponentProps;
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback } from 'react';
3
3
  import extractInputProps from '../../utility/extract-input-props';
4
4
  import Autocomplete from '../Autocomplete';
5
+ import CSVInput from '../CSVInput';
5
6
  import Dropdown from '../Dropdown';
6
7
  import FormField from '../FormField';
7
8
  import PointsInput from '../PointsInput';
@@ -13,6 +14,11 @@ export function AutocompleteFormField(props) {
13
14
  const renderInput = useCallback((renderProps) => (_jsx(Autocomplete, { ...renderProps, ...inputProps })), [inputProps]);
14
15
  return _jsx(FormField, { ...fieldProps, render: renderInput });
15
16
  }
17
+ export function CSVFormField(props) {
18
+ const { fieldProps, inputProps } = extractInputProps(props);
19
+ const renderInput = useCallback((renderProps) => (_jsx(CSVInput, { ...renderProps, ...inputProps })), [inputProps]);
20
+ return _jsx(FormField, { ...fieldProps, render: renderInput });
21
+ }
16
22
  export function DropdownFormField(props) {
17
23
  const { fieldProps, inputProps } = extractInputProps(props);
18
24
  const renderInput = useCallback((renderProps) => (_jsx(Dropdown, { ...renderProps, ...inputProps })), [inputProps]);
@@ -1,6 +1,8 @@
1
1
  export * from './interfaces';
2
2
  export type { AutocompleteProps } from './components/Autocomplete';
3
3
  export { default as Autocomplete } from './components/Autocomplete';
4
+ export type { CSVInputClassNames, CSVInputProps } from './components/CSVInput';
5
+ export { default as CSVInput } from './components/CSVInput';
4
6
  export type { DropdownClassNames, DropdownProps } from './components/Dropdown';
5
7
  export { default as Dropdown } from './components/Dropdown';
6
8
  export type { EditableFormClassNames, EditableFormProps } from './components/EditableForm';
@@ -29,8 +31,8 @@ export type { TextInputClassNames, TextInputProps } from './components/TextInput
29
31
  export { default as TextInput } from './components/TextInput';
30
32
  export type { EditableDropdownFormFieldProps, EditableTextFormFieldProps, } from './components/EditableFormFields';
31
33
  export { EditableDropdownFormField, EditableTextFormField } from './components/EditableFormFields';
32
- export type { AutocompleteFormFieldProps, DropdownFormFieldProps, MoneyFormFieldProps, NumberFormFieldProps, PasswordFormFieldProps, TextFormFieldProps, } from './components/FormFields';
33
- export { AutocompleteFormField, DropdownFormField, MoneyFormField, NumberFormField, PasswordFormField, TextFormField, } from './components/FormFields';
34
+ export type { AutocompleteFormFieldProps, CSVFormFieldProps, DropdownFormFieldProps, MoneyFormFieldProps, NumberFormFieldProps, PasswordFormFieldProps, TextFormFieldProps, } from './components/FormFields';
35
+ export { AutocompleteFormField, CSVFormField, DropdownFormField, MoneyFormField, NumberFormField, PasswordFormField, TextFormField, } from './components/FormFields';
34
36
  export type { UseAutocompleteArgs, UseAutocompleteReturn } from './hooks/useAutocomplete';
35
37
  export { default as useAutocomplete } from './hooks/useAutocomplete';
36
38
  export type { UseDropdownArgs, UseDropdownReturn } from './hooks/useDropdown';
@@ -1,5 +1,6 @@
1
1
  export * from './interfaces';
2
2
  export { default as Autocomplete } from './components/Autocomplete';
3
+ export { default as CSVInput } from './components/CSVInput';
3
4
  export { default as Dropdown } from './components/Dropdown';
4
5
  export { default as EditableForm } from './components/EditableForm';
5
6
  export { default as EditableFormField } from './components/EditableFormField';
@@ -14,7 +15,7 @@ export { default as PointsInput } from './components/PointsInput';
14
15
  export { default as StaticTextInput } from './components/StaticTextInput';
15
16
  export { default as TextInput } from './components/TextInput';
16
17
  export { EditableDropdownFormField, EditableTextFormField } from './components/EditableFormFields';
17
- export { AutocompleteFormField, DropdownFormField, MoneyFormField, NumberFormField, PasswordFormField, TextFormField, } from './components/FormFields';
18
+ export { AutocompleteFormField, CSVFormField, DropdownFormField, MoneyFormField, NumberFormField, PasswordFormField, TextFormField, } from './components/FormFields';
18
19
  export { default as useAutocomplete } from './hooks/useAutocomplete';
19
20
  export { default as useDropdown } from './hooks/useDropdown';
20
21
  export { default as useEditableForm } from './hooks/useEditableForm';
@@ -0,0 +1,2 @@
1
+ declare function formatFileSize(size: number): string;
2
+ export default formatFileSize;
@@ -0,0 +1,11 @@
1
+ const BYTES_IN_KB = 1024;
2
+ const DECIMAL_PLACES = 3;
3
+ function formatFileSize(size) {
4
+ const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
5
+ const exponent = Math.min(Math.floor(Math.log(size) / Math.log(BYTES_IN_KB)), units.length - 1);
6
+ const approx = size / BYTES_IN_KB ** exponent;
7
+ return exponent === 0
8
+ ? `${size} bytes`
9
+ : `${approx.toFixed(DECIMAL_PLACES)} ${units[exponent]} (${size} bytes)`;
10
+ }
11
+ export default formatFileSize;
@@ -20,6 +20,7 @@ import type { ToastClassNames } from '../../components/utility/Toast';
20
20
  import type { FilterBarClassNames } from '../../filters/components/FilterBar';
21
21
  import type { FilterItemClassNames } from '../../filters/components/FilterItem';
22
22
  import type { FilterPanelClassNames } from '../../filters/components/FilterPanel';
23
+ import type { CSVInputClassNames } from '../../forms/components/CSVInput';
23
24
  import type { DropdownClassNames } from '../../forms/components/Dropdown';
24
25
  import type { EditableFormClassNames } from '../../forms/components/EditableForm';
25
26
  import type { EditableFormFieldClassNames } from '../../forms/components/EditableFormField';
@@ -38,6 +39,7 @@ export interface RegisteredClassNames {
38
39
  appNavigation?: ComponentClassNames<AppNavigationClassNames>;
39
40
  button?: ComponentClassNames<ButtonClassNames>;
40
41
  container?: ComponentClassNames<ContainerClassNames>;
42
+ csvInput?: ComponentClassNames<CSVInputClassNames>;
41
43
  dataTable?: ComponentClassNames<DataTableClassNames>;
42
44
  dropdown?: ComponentClassNames<DropdownClassNames>;
43
45
  editableForm?: ComponentClassNames<EditableFormClassNames>;
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.21",
4
+ "version": "4.0.0-alpha.22",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "license": "ISC",
@@ -48,7 +48,7 @@
48
48
  "react-dom": "^19.2.1",
49
49
  "tailwind-merge": "^3.4.0",
50
50
  "use-deep-compare-effect": "^1.8.1",
51
- "@sqrzro/utility": "^4.0.0-alpha.8"
51
+ "@sqrzro/utility": "^4.0.0-alpha.9"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@storybook/addon-a11y": "^10.1.7",