@inkra/react 0.4.3 → 0.6.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 (77) hide show
  1. package/README.md +109 -0
  2. package/dist/audio-player/index.js +6 -4
  3. package/dist/breadcrumb/index.d.ts +16 -4
  4. package/dist/breadcrumb/index.js +24 -8
  5. package/dist/calendar/index.js +10 -4
  6. package/dist/calendar/locales.d.ts +7 -0
  7. package/dist/calendar/locales.js +2 -0
  8. package/dist/carousel/index.js +8 -5
  9. package/dist/choice-grid/index.js +3 -1
  10. package/dist/color-picker/GradientEditor.d.ts +2 -1
  11. package/dist/color-picker/GradientEditor.js +10 -9
  12. package/dist/color-picker/index.js +12 -10
  13. package/dist/content/index.js +4 -1
  14. package/dist/data-table/index.d.ts +1 -1
  15. package/dist/data-table/index.js +4 -1
  16. package/dist/date-picker/index.js +11 -13
  17. package/dist/day-agenda/index.d.ts +1 -1
  18. package/dist/day-agenda/index.js +4 -1
  19. package/dist/dockable-panel/index.js +10 -8
  20. package/dist/drawer/index.js +7 -2
  21. package/dist/editable/index.js +8 -5
  22. package/dist/form/Input.js +4 -1
  23. package/dist/form/Textarea.js +4 -2
  24. package/dist/heatmap/index.js +4 -1
  25. package/dist/hotkey/index.js +4 -1
  26. package/dist/i18n/catalog.d.ts +5 -0
  27. package/dist/i18n/catalog.js +289 -0
  28. package/dist/i18n/index.d.ts +23 -0
  29. package/dist/i18n/index.js +50 -0
  30. package/dist/image-cropper/index.js +17 -15
  31. package/dist/image-viewer/VideoResource.js +14 -12
  32. package/dist/image-viewer/index.js +23 -21
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +2 -0
  35. package/dist/input-number/index.js +4 -1
  36. package/dist/internal/textRenderer.d.ts +1 -0
  37. package/dist/internal/textRenderer.js +10 -2
  38. package/dist/journey-map/index.d.ts +1 -1
  39. package/dist/journey-map/index.js +6 -3
  40. package/dist/keypad/index.js +4 -1
  41. package/dist/log/index.js +13 -11
  42. package/dist/markdown/index.js +11 -9
  43. package/dist/node-graph/index.d.ts +1 -1
  44. package/dist/node-graph/index.js +14 -3
  45. package/dist/number-ticker/index.js +4 -1
  46. package/dist/overflow-list/index.js +4 -4
  47. package/dist/overlay/Overlay.js +7 -2
  48. package/dist/pagination/index.js +7 -5
  49. package/dist/pan-zoom/index.js +6 -3
  50. package/dist/radar/index.js +4 -1
  51. package/dist/range-slider/index.js +4 -3
  52. package/dist/rating/index.js +5 -2
  53. package/dist/resizable/index.d.ts +1 -1
  54. package/dist/resizable/index.js +4 -1
  55. package/dist/select/SearchableSelect.js +3 -1
  56. package/dist/select/Select.js +3 -1
  57. package/dist/select/StepperSelect.js +5 -2
  58. package/dist/sortable/index.js +11 -9
  59. package/dist/sparkline/index.js +7 -7
  60. package/dist/steps/index.js +4 -1
  61. package/dist/styles/breadcrumb.css +96 -5
  62. package/dist/styles/select.css +8 -6
  63. package/dist/styles.css +104 -11
  64. package/dist/tabs/index.js +4 -1
  65. package/dist/tag-input/index.js +12 -17
  66. package/dist/theme/index.d.ts +11 -0
  67. package/dist/theme/index.js +23 -0
  68. package/dist/time-picker/index.js +7 -7
  69. package/dist/timeline/index.js +4 -1
  70. package/dist/toast/index.d.ts +1 -1
  71. package/dist/toast/index.js +4 -1
  72. package/dist/transfer/index.js +30 -27
  73. package/dist/tree/index.js +7 -2
  74. package/dist/tree-select/index.js +3 -1
  75. package/dist/upload/index.js +21 -18
  76. package/dist/virtual-list/index.js +4 -1
  77. package/package.json +11 -1
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { forwardRef, useEffect, useId, useMemo, useRef, useState } from 'react';
3
4
  import { Button } from '../Button.js';
4
5
  import { Input } from '../form/Input.js';
@@ -12,29 +13,29 @@ const searchPaths = [
12
13
  { d: 'M 11 4 A 7 7 0 1 0 11 18 A 7 7 0 1 0 11 4 M 16 16 L 21 21' },
13
14
  ];
14
15
  const rightPaths = [{ d: 'M 5 12 L 19 12 M 13 6 L 19 12 L 13 18' }];
15
- const defaultLabels = {
16
- sourceTitle: 'Available',
17
- targetTitle: 'Selected',
18
- sourceSearchLabel: 'Search available items',
19
- targetSearchLabel: 'Search selected items',
20
- sourceClearSearch: 'Clear available item search',
21
- targetClearSearch: 'Clear selected item search',
22
- sourceSearchPlaceholder: 'Search available',
23
- targetSearchPlaceholder: 'Search selected',
24
- selectAllSource: 'Select all visible available items',
25
- selectAllTarget: 'Select all visible selected items',
26
- sourceEmpty: 'No available items',
27
- targetEmpty: 'No selected items',
28
- sourceNoResults: 'No available items match this search',
29
- targetNoResults: 'No selected items match this search',
30
- moveToTarget: 'Move to selected',
31
- moveToSource: 'Move to available',
32
- moveSelectedToTarget: 'Move selected visible items to selected',
33
- moveSelectedToSource: 'Move selected visible items to available',
34
- batchActions: 'Transfer selected visible items',
35
- previousPage: 'Previous page',
36
- nextPage: 'Next page',
37
- };
16
+ const defaultLabels = (t) => ({
17
+ sourceTitle: t('transfer.available'),
18
+ targetTitle: t('common.selected'),
19
+ sourceSearchLabel: t('transfer.sourceSearch'),
20
+ targetSearchLabel: t('transfer.targetSearch'),
21
+ sourceClearSearch: t('transfer.sourceClear'),
22
+ targetClearSearch: t('transfer.targetClear'),
23
+ sourceSearchPlaceholder: t('transfer.sourcePlaceholder'),
24
+ targetSearchPlaceholder: t('transfer.targetPlaceholder'),
25
+ selectAllSource: t('transfer.allSource'),
26
+ selectAllTarget: t('transfer.allTarget'),
27
+ sourceEmpty: t('transfer.sourceEmpty'),
28
+ targetEmpty: t('transfer.targetEmpty'),
29
+ sourceNoResults: t('transfer.sourceNoResults'),
30
+ targetNoResults: t('transfer.targetNoResults'),
31
+ moveToTarget: t('transfer.toTarget'),
32
+ moveToSource: t('transfer.toSource'),
33
+ moveSelectedToTarget: t('transfer.selectedToTarget'),
34
+ moveSelectedToSource: t('transfer.selectedToSource'),
35
+ batchActions: t('transfer.batch'),
36
+ previousPage: t('common.previousPage'),
37
+ nextPage: t('common.nextPage'),
38
+ });
38
39
  function searchableText(item) {
39
40
  const parts = [
40
41
  item.searchText,
@@ -52,15 +53,17 @@ function filterPanelItems(items, query, side, filterItem) {
52
53
  const needle = query.trim().toLocaleLowerCase();
53
54
  return items.filter(item => searchableText(item).includes(needle));
54
55
  }
55
- function defaultCount({ visible, total, selected }) {
56
- return selected > 0 ? `${selected} selected · ${visible}/${total}` : `${visible}/${total}`;
56
+ function defaultCount({ visible, total, selected }, t) {
57
+ return selected > 0 ? t('transfer.count', { selected, visible, total }) : `${visible}/${total}`;
57
58
  }
58
59
  function itemCopy(item) {
59
60
  return _jsxs("span", { className: "inkra-transfer__item-copy", children: [_jsx("span", { className: "inkra-transfer__item-label", children: typeof item.label === 'string' || typeof item.label === 'number'
60
61
  ? _jsx(PencilText, { children: String(item.label) })
61
62
  : item.label }), item.description == null ? null : _jsx("span", { className: "inkra-transfer__item-description", children: item.description })] });
62
63
  }
63
- export const Transfer = forwardRef(function Transfer({ className, defaultTargetKeys = [], disabled = false, filterItem, formatCount = defaultCount, items, labels: labelOverrides, onTargetKeysChange, pageSize, pageLabel = (page, count) => `${page} / ${count}`, renderItem, searchable = true, targetKeys, ...props }, ref) {
64
+ export const Transfer = forwardRef(function Transfer({ className, defaultTargetKeys = [], disabled = false, filterItem, formatCount: formatCountProp, items, labels: labelOverrides, onTargetKeysChange, pageSize, pageLabel = (page, count) => `${page} / ${count}`, renderItem, searchable = true, targetKeys, ...props }, ref) {
65
+ const { t } = useInkraLocale();
66
+ const formatCount = formatCountProp ?? ((details) => defaultCount(details, t));
64
67
  const generatedId = useId();
65
68
  const controlled = targetKeys !== undefined;
66
69
  const uniqueItems = useMemo(() => uniqueTransferItems(items), [items]);
@@ -70,7 +73,7 @@ export const Transfer = forwardRef(function Transfer({ className, defaultTargetK
70
73
  const currentTargetSet = useMemo(() => new Set(currentTargetKeys), [currentTargetKeys]);
71
74
  const itemsById = useMemo(() => new Map(uniqueItems.map(item => [item.id, item])), [uniqueItems]);
72
75
  const panels = useMemo(() => splitTransferItems(uniqueItems, currentTargetKeys), [uniqueItems, currentTargetKeys]);
73
- const labels = { ...defaultLabels, ...labelOverrides };
76
+ const labels = { ...defaultLabels(t), ...labelOverrides };
74
77
  const [queries, setQueries] = useState({ source: '', target: '' });
75
78
  const [pages, setPages] = useState({ source: 0, target: 0 });
76
79
  const pageLength = pageSize !== undefined && Number.isFinite(pageSize) ? Math.max(1, Math.floor(pageSize)) : undefined;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { Tree as AriaTree, TreeItem as AriaTreeItem, TreeItemContent as AriaTreeItemContent, } from 'react-aria-components/Tree';
3
4
  import { Button as AriaButton } from 'react-aria-components/Button';
4
5
  import { forwardRef } from 'react';
@@ -6,14 +7,18 @@ import { PencilIcon } from '../PencilIcon.js';
6
7
  import { PencilSurface } from '../PencilSurface.js';
7
8
  import { PencilText } from '../PencilText.js';
8
9
  const chevronIcon = [{ d: 'M 7 10 L 12 15 L 17 10' }];
9
- function TreeRootInner({ className, style, indent = 24, size = 'md', emptyState = 'No items', selectionMode = 'single', selectionBehavior = 'toggle', ...props }, ref) {
10
+ function TreeRootInner(inkraProps, ref) {
11
+ const { t } = useInkraLocale();
12
+ const { className, style, indent = 24, size = 'md', emptyState = t('common.noItems'), selectionMode = 'single', selectionBehavior = 'toggle', ...props } = inkraProps;
10
13
  return _jsx(AriaTree, { ...props, ref: ref, className: ['inkra-tree', className].filter(Boolean).join(' '), "data-size": size, style: { '--inkra-tree-indent': `${Number.isFinite(indent) ? Math.max(0, indent) : 24}px`, ...style }, selectionMode: selectionMode, selectionBehavior: selectionBehavior, renderEmptyState: () => _jsx("div", { className: "inkra-tree__empty", children: emptyState }) });
11
14
  }
12
15
  const TreeRoot = forwardRef(TreeRootInner);
13
16
  const TreeItem = forwardRef(function TreeItem({ className, ...props }, ref) {
14
17
  return _jsx(AriaTreeItem, { ...props, ref: ref, className: ['inkra-tree__item', className].filter(Boolean).join(' ') });
15
18
  });
16
- const TreeItemContent = forwardRef(function TreeItemContent({ children, className, icon, disclosureIcon, expandLabel = 'Expand item', collapseLabel = 'Collapse item', }, ref) {
19
+ const TreeItemContent = forwardRef(function TreeItemContent(inkraProps, ref) {
20
+ const { t } = useInkraLocale();
21
+ const { children, className, icon, disclosureIcon, expandLabel = t('tree.expand'), collapseLabel = t('tree.collapse'), } = inkraProps;
17
22
  return _jsx(AriaTreeItemContent, { ref: ref, children: ({ hasChildItems, isExpanded, isSelected, isFocusVisible, isDisabled, level, id, }) => {
18
23
  const state = { isExpanded, hasChildItems, isSelected, isDisabled, level };
19
24
  const resolvedIcon = typeof icon === 'function' ? icon(state) : icon;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { forwardRef, useEffect, useId, useMemo, useRef, useState, } from 'react';
3
4
  import { Button, ButtonGroup } from '../Button.js';
4
5
  import { Input } from '../form/Input.js';
@@ -56,7 +57,8 @@ const TreeSelectTree = forwardRef(function TreeSelectTree({ nodes, renderLabel,
56
57
  return _jsx(Tree.Root, { "aria-label": treeLabel, className: "inkra-tree-select__tree", emptyState: emptyContent, expandedKeys: effectiveExpanded, onExpandedChange: onExpandedChange, onSelectionChange: onSelectionChange, ref: ref, selectedKeys: selectedSet, selectionMode: multiple ? 'multiple' : 'single', children: treeItems });
57
58
  });
58
59
  export const TreeSelect = forwardRef(function TreeSelect(props, ref) {
59
- const { items, branchSelection = 'leaf', selectionPropagation = 'none', renderLabel, renderValue, placeholder = 'Select an item', searchPlaceholder = 'Search tree', emptyContent = 'No matching items', clearable = true, clearLabel = 'Clear selection', treeLabel = 'Tree options', expandLabel = 'Expand item', collapseLabel = 'Collapse item', size = 'md', name, form, disabled, readOnly, required, invalid, open: controlledOpen, defaultOpen = false, onOpenChange, expandedIds, defaultExpandedIds = [], onExpandedIdsChange, side = 'bottom', align = 'start', sideOffset = 8, collisionPadding = 8, id, className, multiple: _multiple, value: _value, defaultValue: _defaultValue, onValueChange: _onValueChange, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, ...rest } = props;
60
+ const { t } = useInkraLocale();
61
+ const { items, branchSelection = 'leaf', selectionPropagation = 'none', renderLabel, renderValue, placeholder = t('treeSelect.placeholder'), searchPlaceholder = t('treeSelect.search'), emptyContent = t('treeSelect.empty'), clearable = true, clearLabel = t('select.clear'), treeLabel = t('treeSelect.options'), expandLabel = t('tree.expand'), collapseLabel = t('tree.collapse'), size = 'md', name, form, disabled, readOnly, required, invalid, open: controlledOpen, defaultOpen = false, onOpenChange, expandedIds, defaultExpandedIds = [], onExpandedIdsChange, side = 'bottom', align = 'start', sideOffset = 8, collisionPadding = 8, id, className, multiple: _multiple, value: _value, defaultValue: _defaultValue, onValueChange: _onValueChange, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, ...rest } = props;
60
62
  const field = useFieldContext();
61
63
  const generatedId = useId();
62
64
  const controlId = field?.controlId ?? id ?? `inkra-tree-select-${generatedId}`;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { forwardRef, memo, useCallback, useEffect, useId, useMemo, useRef, useState, } from 'react';
3
4
  import { Button } from '../Button.js';
4
5
  import { Progress } from '../display/index.js';
@@ -10,23 +11,23 @@ const uploadPaths = [
10
11
  { d: 'm17 8-5-5-5 5' },
11
12
  { d: 'M12 3v12' },
12
13
  ];
13
- const defaultLabels = {
14
- browse: 'Choose files',
15
- drop: 'or drop files here',
16
- selected: 'Selected',
17
- upload: 'Upload',
18
- uploading: 'Uploading',
19
- success: 'Uploaded',
20
- error: 'Upload failed',
21
- cancelled: 'Cancelled',
22
- retry: 'Retry',
23
- cancel: 'Cancel',
24
- remove: 'Remove',
25
- rejected: 'Files not added',
26
- acceptError: 'This file type is not accepted.',
27
- sizeError: maxSize => `File exceeds the ${formatBytes(maxSize)} limit.`,
28
- countError: maxFiles => `Only ${maxFiles} file${maxFiles === 1 ? '' : 's'} may be selected.`,
29
- };
14
+ const defaultLabels = (t) => ({
15
+ browse: t('upload.browse'),
16
+ drop: t('upload.drop'),
17
+ selected: t('common.selected'),
18
+ upload: t('upload.upload'),
19
+ uploading: t('upload.uploading'),
20
+ success: t('upload.success'),
21
+ error: t('upload.failed'),
22
+ cancelled: t('upload.cancelled'),
23
+ retry: t('common.retry'),
24
+ cancel: t('common.cancel'),
25
+ remove: t('common.remove'),
26
+ rejected: t('upload.rejected'),
27
+ acceptError: t('upload.acceptError'),
28
+ sizeError: maxSize => t('upload.sizeError', { size: formatBytes(maxSize) }),
29
+ countError: maxFiles => t(maxFiles === 1 ? 'upload.countErrorOne' : 'upload.countError', { count: maxFiles }),
30
+ });
30
31
  function formatBytes(value) {
31
32
  if (value < 1024)
32
33
  return `${value} B`;
@@ -60,6 +61,7 @@ const UploadRow = memo(function UploadRow({ item, labels, disabled, canUpload, s
60
61
  return _jsx("li", { className: "inkra-upload__item", "data-status": item.status, children: _jsxs(PencilSurface, { className: "inkra-upload__item-surface", variant: "paper", radius: 8, children: [_jsxs("div", { className: "inkra-upload__file", children: [_jsx(PencilText, { className: "inkra-upload__name", ellipsis: true, children: item.file.name }), _jsxs("span", { className: "inkra-upload__meta", children: [formatBytes(item.file.size), " \u00B7 ", statusLabel(item.status, labels)] }), item.status === 'uploading' && _jsx(Progress, { size: "sm", value: item.progress ?? 0, "aria-label": `${item.file.name}: ${Math.round(item.progress ?? 0)}%` }), item.error && _jsx("span", { className: "inkra-upload__error", children: item.error })] }), _jsxs("div", { className: "inkra-upload__actions", children: [item.status === 'uploading' && _jsx(Button, { disabled: disabled, size: "sm", variant: "ghost", onClick: () => cancel(item.id), children: labels.cancel }), canUpload && item.status === 'selected' && _jsx(Button, { disabled: disabled, size: "sm", variant: "ghost", onClick: () => startUpload(item.id), children: labels.upload }), canUpload && (item.status === 'error' || item.status === 'cancelled') && _jsx(Button, { disabled: disabled, size: "sm", variant: "ghost", onClick: () => startUpload(item.id, 'retry'), children: labels.retry }), _jsx(Button, { disabled: disabled, size: "sm", variant: "ghost", onClick: () => remove(item.id), children: labels.remove })] })] }) });
61
62
  });
62
63
  export const Upload = forwardRef(function Upload({ accept, autoUpload = true, capture, className, defaultFiles = [], disabled = false, files, inputProps, labels: labelOverrides, maxFiles = Number.POSITIVE_INFINITY, maxSize = Number.POSITIVE_INFINITY, multiple = true, onFilesChange, onReject, onUpload, validate, 'aria-label': ariaLabel, ...props }, ref) {
64
+ const { t } = useInkraLocale();
63
65
  const instanceId = useId();
64
66
  const inputRef = useRef(null);
65
67
  const itemCounter = useRef(0);
@@ -74,7 +76,8 @@ export const Upload = forwardRef(function Upload({ accept, autoUpload = true, ca
74
76
  const itemsRef = useRef(items);
75
77
  itemsRef.current = items;
76
78
  disabledRef.current = disabled;
77
- const labelValues = { ...defaultLabels, ...labelOverrides };
79
+ const localizedDefaults = useMemo(() => defaultLabels(t), [t]);
80
+ const labelValues = { ...localizedDefaults, ...labelOverrides };
78
81
  // Snapshot scalar/function values so same-object label edits remain observable.
79
82
  const labels = useMemo(() => labelValues, [
80
83
  labelValues.browse, labelValues.drop, labelValues.selected, labelValues.upload,
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createElement as _createElement } from "react";
3
+ import { useInkraLocale } from '../i18n/index.js';
3
4
  import { useVirtualizer } from '@tanstack/react-virtual';
4
5
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState, } from 'react';
5
6
  import { PencilSurface } from '../PencilSurface.js';
@@ -12,7 +13,9 @@ function classes(...values) {
12
13
  function toEngineScrollOptions(options) {
13
14
  return options;
14
15
  }
15
- function VirtualListInner({ 'aria-label': ariaLabel, className, empty, emptyLabel = 'No items', estimateSize, gap, getItemProps, getScrollElement, height, initialOffset = 0, itemKey, items, listLabel, listRole = 'list', measureItems = false, onKeyDown, overscan, paddingEnd, paddingStart, renderItem, role: roleProp, scrollMargin, ssrItemCount, style, tabIndex, ...props }, forwardedRef) {
16
+ function VirtualListInner(inkraProps, forwardedRef) {
17
+ const { t } = useInkraLocale();
18
+ const { 'aria-label': ariaLabel, className, empty, emptyLabel = t('common.noItems'), estimateSize, gap, getItemProps, getScrollElement, height, initialOffset = 0, itemKey, items, listLabel, listRole = 'list', measureItems = false, onKeyDown, overscan, paddingEnd, paddingStart, renderItem, role: roleProp, scrollMargin, ssrItemCount, style, tabIndex, ...props } = inkraProps;
16
19
  const rootRef = useRef(null);
17
20
  const itemsRef = useRef(items);
18
21
  itemsRef.current = items;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkra/react",
3
- "version": "0.4.3",
3
+ "version": "0.6.0",
4
4
  "description": "Hand-drawn React controls with native semantics and deterministic decoration.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,6 +21,16 @@
21
21
  "import": "./dist/index.js",
22
22
  "default": "./dist/index.js"
23
23
  },
24
+ "./theme": {
25
+ "types": "./dist/theme/index.d.ts",
26
+ "import": "./dist/theme/index.js",
27
+ "default": "./dist/theme/index.js"
28
+ },
29
+ "./i18n": {
30
+ "types": "./dist/i18n/index.d.ts",
31
+ "import": "./dist/i18n/index.js",
32
+ "default": "./dist/i18n/index.js"
33
+ },
24
34
  "./PencilSurface": {
25
35
  "types": "./dist/PencilSurface.d.ts",
26
36
  "import": "./dist/PencilSurface.js",