@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
package/README.md CHANGED
@@ -48,6 +48,104 @@ export function Actions() {
48
48
 
49
49
  To use only the design tokens, import `@inkra/react/tokens.css` instead.
50
50
 
51
+ ## Theme Provider
52
+
53
+ ```tsx
54
+ import '@inkra/react/styles.css';
55
+ import { ThemeProvider } from '@inkra/react/theme';
56
+ import type { InkraThemeTokens } from '@inkra/react/theme';
57
+
58
+ const tokens = {
59
+ '--inkra-accent': '#4b96aa',
60
+ '--inkra-icon-stroke-width': '1.5px',
61
+ } satisfies InkraThemeTokens;
62
+
63
+ <ThemeProvider theme="auto" tokens={tokens}>
64
+ <App />
65
+ </ThemeProvider>
66
+ ```
67
+
68
+ `ThemeProvider` is also exported from the package root. It renders a local `div`
69
+ scope, accepts its normal HTML attributes and forwards its ref. It never changes
70
+ the document theme or stores a preference. `theme` supports `light`, `dark`, and
71
+ `auto`; omit it to inherit the surrounding CSS theme. Auto uses native CSS media
72
+ queries and works with SSR without resolving the system preference in JavaScript.
73
+
74
+ `tokens` accepts partial `--inkra-*` variables for color, fonts, geometry and
75
+ pencil rendering. Supply units for lengths (`"1.5px"`); unitless numbers work for
76
+ intensity and similar values. An inner scope without `theme` inherits outer
77
+ tokens; an explicit mode establishes that mode's built-in variables before local
78
+ overrides. `style` takes precedence over `tokens`. User token overrides are
79
+ suspended while forced colors are active so the system palette can take over.
80
+
81
+ Custom colors do not automatically adapt to light/dark modes. Supply appropriate
82
+ variables for each mode, or use CSS media queries. Existing component portals
83
+ inherit the trigger scope and track its live changes. The scope has no default
84
+ background or spacing; use a surface or your own layout when needed. It composes
85
+ with `InkraProvider` for language configuration without changing input state.
86
+
87
+ ## Internationalization
88
+
89
+ Wrap your application in `InkraProvider` to localize component-owned copy and
90
+ accessible labels. Built-in dictionaries are `en`, `zh-CN`, `ja`, `ko`, and `fr`.
91
+ All five share the same typed keys. Regional tags such as `en-GB`, `ja-JP`,
92
+ `ko-KR`, `fr-CA` and `zh-Hans-CN` select the matching language while keeping
93
+ regional date/number formatting. No provider preserves the English defaults.
94
+
95
+ ```tsx
96
+ import { InkraProvider, Input, Pagination } from '@inkra/react';
97
+ import type { InkraDictionary } from '@inkra/react/i18n';
98
+
99
+ const messages = {
100
+ 'input.clear': 'Clear note',
101
+ 'pagination.page': 'Open page {page}',
102
+ } satisfies InkraDictionary;
103
+
104
+ <InkraProvider locale="fr-CA" messages={messages}>
105
+ <Input clearable defaultValue="My note" aria-label="Note" />
106
+ <Pagination totalPages={5} />
107
+ </InkraProvider>;
108
+ ```
109
+
110
+ `messages` is a partial dictionary; developers can import JSON or adapt their
111
+ existing i18n resource into these flat keys. Inspect `inkraLocales.en` for the
112
+ complete key list and source templates, or copy another built-in dictionary.
113
+ The provider, dictionaries, types, `useInkraLocale` and
114
+ `createInkraTranslator` are also exported from `@inkra/react/i18n`.
115
+
116
+ - Precedence: explicit component labels/props, inner provider overrides, inherited
117
+ overrides, then the built-in dictionary. Unknown languages use English copy
118
+ while valid `Intl` locale formatting is preserved. Invalid locale strings use `en-US`.
119
+ - Nested providers merge by key, even when the inner locale changes. Pass the
120
+ desired override dictionary on language changes; inherited overrides are intentional.
121
+ - `undefined` entries do not override; empty strings do. Avoid empty accessible
122
+ labels on interactive controls. Use `satisfies InkraDictionary` to catch key typos.
123
+ - Preserve template placeholders such as `{page}`, `{count}` and `{label}`.
124
+ Interpolation is a single literal-text pass, not HTML, ICU, or executable code.
125
+ Missing placeholder values are left visible rather than silently removed.
126
+ - Explicit component `locale` props take priority over the provider. Calendar
127
+ also retains its DayPicker locale/labels/formatters API. NumberTicker's
128
+ explicit compact-unit policies remain independent of regional number formatting.
129
+ - Language and dictionary updates do not remount children or clear form drafts.
130
+ The provider adds no HTML wrapper and does not mutate the document language.
131
+ Set `lang` on your page or locale-scoped container; set `dir` separately if needed.
132
+ - Application labels, option content, errors supplied by your application, and
133
+ stored/submitted values are not automatically translated. InkraProvider also
134
+ passes its locale to React Aria's internal announcements; third-party/native
135
+ messages are not keys in the Inkra dictionary.
136
+ - Use identical locale/dictionary data for SSR and hydration. Messages may change
137
+ after hydration; the provider does not read browser language or local storage.
138
+
139
+ ```tsx
140
+ import { InkraProvider, inkraLocales } from '@inkra/react/i18n';
141
+
142
+ const customLanguage = { ...inkraLocales.en, 'input.clear': 'Effacer la note' };
143
+
144
+ <InkraProvider locale="fr" messages={customLanguage}>
145
+ {children}
146
+ </InkraProvider>;
147
+ ```
148
+
51
149
  ## Button
52
150
 
53
151
  `Button` forwards its ref and native button attributes. Its default `type` is `button`, so placing it inside a form will not submit unexpectedly.
@@ -189,6 +287,17 @@ Canvas failures, forced colors and printing retain an SVG progress fallback.
189
287
 
190
288
  ## Menus And Content
191
289
 
290
+ `Breadcrumb` exposes `Root`, `List`, `Item`, `Link`, `MenuTrigger`,
291
+ `ContextLink`, `Page` and `Separator`. `Link` renders a real anchor and defaults
292
+ to the pencil-underlined `link` appearance; `startIcon` and `endIcon` occupy the
293
+ logical sides and follow RTL. `Page` accepts the same icon slots. Page, link and
294
+ menu-trigger content share a 44px default hit height, 15px text, 20px icon slot,
295
+ 8px gap and 12px logical padding so a header can change item semantics without
296
+ moving its baseline. Disabled links remove `href` and leave the tab order. Use
297
+ `MenuTrigger` inside `DropdownMenu.Root` for an item that selects another path,
298
+ and `ContextLink` inside `ContextMenu.Root` for pointer, keyboard and optional
299
+ long-press actions without adding a second focus target.
300
+
192
301
  `DropdownMenu` exposes `Root`, `Trigger`, `Portal`, `Content`, `Item`, `CheckboxItem`, `RadioGroup`, `RadioItem`, `Group`, `Label` and `Separator`. It supports controlled open state, placement/collision props, disabled items, keyboard navigation and focus return. Use `Trigger asChild` with a Button; use `Item.destructive` for dangerous actions. Portal content inherits the trigger theme. Submenus are not included yet.
193
302
 
194
303
  `Alert.Root` accepts `variant="info | success | warning | danger"`, an optional decorative `icon`, and native attributes. Compose with `Alert.Title` and `Alert.Description`. The default `role="status"` is polite; explicitly use `role="alert"` only for urgent feedback.
@@ -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, useCallback, useEffect, useId, useImperativeHandle, useRef, useState } from 'react';
3
4
  import { Button } from '../Button.js';
4
5
  import { PencilIcon } from '../PencilIcon.js';
@@ -21,14 +22,15 @@ const mutePaths = [
21
22
  { d: 'm16.5 14.5 5-5' },
22
23
  { d: 'm16.5 9.5 5 5' },
23
24
  ];
24
- const DEFAULT_LABELS = {
25
- controls: 'Audio controls', audio: 'Audio', play: 'Play', pause: 'Pause', mute: 'Mute', unmute: 'Unmute', seek: 'Seek', volume: 'Volume', rate: 'Playback speed', loading: 'Loading audio', blocked: 'Playback was blocked. Press play to try again.', error: 'Audio failed to load.',
26
- };
25
+ const DEFAULT_LABELS = (t) => ({
26
+ controls: t('audio.controls'), audio: t('audio.label'), play: t('audio.play'), pause: t('audio.pause'), mute: t('audio.mute'), unmute: t('audio.unmute'), seek: t('audio.seek'), volume: t('audio.volume'), rate: t('audio.rate'), loading: t('audio.loading'), blocked: t('audio.blocked'), error: t('audio.error'),
27
+ });
27
28
  const rates = [0.75, 1, 1.25, 1.5, 2];
28
29
  function clamp(value, min, max) { return Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : min; }
29
30
  function formatTime(value) { if (!Number.isFinite(value) || value < 0)
30
31
  return '0:00'; const seconds = Math.floor(value); return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`; }
31
32
  export const AudioPlayer = forwardRef(function AudioPlayer({ src, preload = 'metadata', autoPlay = false, crossOrigin, className, defaultMuted = false, defaultPlaybackRate = 1, defaultVolume = 1, volume, muted, playbackRate, onVolumeChange, onMutedChange, onPlaybackRateChange, onTimeChange, onStateChange, onError, labels, renderControls, audioProps, ...props }, forwardedRef) {
33
+ const { t } = useInkraLocale();
32
34
  const id = useId();
33
35
  const audioRef = useRef(null);
34
36
  const rootRef = useRef(null);
@@ -42,7 +44,7 @@ export const AudioPlayer = forwardRef(function AudioPlayer({ src, preload = 'met
42
44
  controlledRef.current = { volume, muted, playbackRate };
43
45
  const callbacksRef = useRef({ onVolumeChange, onMutedChange, onPlaybackRateChange, onTimeChange, onStateChange, onError });
44
46
  callbacksRef.current = { onVolumeChange, onMutedChange, onPlaybackRateChange, onTimeChange, onStateChange, onError };
45
- const mergedLabels = { ...DEFAULT_LABELS, ...labels };
47
+ const mergedLabels = { ...DEFAULT_LABELS(t), ...labels };
46
48
  const update = useCallback((patch) => {
47
49
  const current = stateRef.current;
48
50
  const changed = Object.keys(patch).some(key => !Object.is(current[key], patch[key]));
@@ -1,16 +1,28 @@
1
- import { type ComponentPropsWithoutRef } from 'react';
2
- import { type ButtonLinkProps } from '../Button.js';
1
+ import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
2
+ import { type ButtonLinkProps, type ButtonProps } from '../Button.js';
3
+ import { type ContextMenuTriggerProps } from '../context-menu/index.js';
3
4
  export type BreadcrumbProps = ComponentPropsWithoutRef<'nav'>;
4
5
  export type BreadcrumbListProps = ComponentPropsWithoutRef<'ol'>;
5
6
  export type BreadcrumbItemProps = ComponentPropsWithoutRef<'li'>;
6
7
  export type BreadcrumbLinkProps = ButtonLinkProps;
7
- export type BreadcrumbPageProps = ComponentPropsWithoutRef<'span'>;
8
+ export type BreadcrumbMenuTriggerProps = ButtonProps;
9
+ export interface BreadcrumbContextLinkProps extends BreadcrumbLinkProps {
10
+ exclude?: ContextMenuTriggerProps['exclude'];
11
+ longPress?: ContextMenuTriggerProps['longPress'];
12
+ nativeContextMenu?: ContextMenuTriggerProps['nativeContextMenu'];
13
+ }
14
+ export interface BreadcrumbPageProps extends ComponentPropsWithoutRef<'span'> {
15
+ endIcon?: ReactNode;
16
+ startIcon?: ReactNode;
17
+ }
8
18
  export type BreadcrumbSeparatorProps = ComponentPropsWithoutRef<'li'>;
9
19
  export declare const Breadcrumb: {
10
20
  Root: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & import("react").RefAttributes<HTMLElement>>;
11
21
  List: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").OlHTMLAttributes<HTMLOListElement>, HTMLOListElement>, "ref"> & import("react").RefAttributes<HTMLOListElement>>;
12
22
  Item: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & import("react").RefAttributes<HTMLLIElement>>;
13
23
  Link: import("react").ForwardRefExoticComponent<ButtonLinkProps & import("react").RefAttributes<HTMLAnchorElement>>;
14
- Page: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & import("react").RefAttributes<HTMLSpanElement>>;
24
+ MenuTrigger: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
25
+ ContextLink: import("react").ForwardRefExoticComponent<BreadcrumbContextLinkProps & import("react").RefAttributes<HTMLAnchorElement>>;
26
+ Page: import("react").ForwardRefExoticComponent<BreadcrumbPageProps & import("react").RefAttributes<HTMLSpanElement>>;
15
27
  Separator: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & import("react").RefAttributes<HTMLLIElement>>;
16
28
  };
@@ -1,10 +1,15 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { forwardRef } from 'react';
3
- import { ButtonLink } from '../Button.js';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
3
+ import { forwardRef, } from 'react';
4
+ import { Button, ButtonLink, } from '../Button.js';
5
+ import { ContextMenu, } from '../context-menu/index.js';
6
+ import { DropdownMenu } from '../dropdown-menu/index.js';
4
7
  import { PencilText } from '../PencilText.js';
5
8
  const classes = (...values) => values.filter(Boolean).join(' ');
6
9
  const copy = (children) => typeof children === 'string' ? _jsx(PencilText, { children: children }) : children;
7
- const Root = forwardRef(function BreadcrumbRoot({ className, 'aria-label': label = 'Breadcrumb', ...props }, ref) {
10
+ const Root = forwardRef(function BreadcrumbRoot(inkraProps, ref) {
11
+ const { t } = useInkraLocale();
12
+ const { className, 'aria-label': label = t('breadcrumb.label'), ...props } = inkraProps;
8
13
  return _jsx("nav", { ...props, ref: ref, "aria-label": label, className: classes('inkra-breadcrumb', className) });
9
14
  });
10
15
  const List = forwardRef(function BreadcrumbList({ className, ...props }, ref) {
@@ -13,13 +18,24 @@ const List = forwardRef(function BreadcrumbList({ className, ...props }, ref) {
13
18
  const Item = forwardRef(function BreadcrumbItem({ className, ...props }, ref) {
14
19
  return _jsx("li", { ...props, ref: ref, className: classes('inkra-breadcrumb__item', className) });
15
20
  });
16
- const Link = forwardRef(function BreadcrumbLink({ className, variant = 'ghost', ...props }, ref) {
21
+ const Link = forwardRef(function BreadcrumbLink({ className, variant = 'link', ...props }, ref) {
17
22
  return _jsx(ButtonLink, { ...props, ref: ref, variant: variant, className: classes('inkra-breadcrumb__link', className) });
18
23
  });
19
- const Page = forwardRef(function BreadcrumbPage({ className, children, ...props }, ref) {
20
- return _jsx("span", { ...props, ref: ref, "aria-current": "page", className: classes('inkra-breadcrumb__page', className), children: copy(children) });
24
+ const MenuTrigger = forwardRef(function BreadcrumbMenuTrigger({ className, disabled = false, loading = false, variant = 'link', ...props }, ref) {
25
+ return _jsx(DropdownMenu.Trigger, { asChild: true, disabled: disabled || loading, children: _jsx(Button, { ...props, ref: ref, disabled: disabled, loading: loading, variant: variant, className: classes('inkra-breadcrumb__trigger', className) }) });
26
+ });
27
+ const ContextLinkAnchor = forwardRef(function BreadcrumbContextLinkAnchor({ 'aria-disabled': ariaDisabled, disabled = false, ...props }, ref) {
28
+ const contextDisabled = ariaDisabled === true || ariaDisabled === 'true';
29
+ return _jsx(Link, { ...props, ref: ref, "aria-disabled": contextDisabled || disabled || undefined, disabled: contextDisabled || disabled });
30
+ });
31
+ const ContextLink = forwardRef(function BreadcrumbContextLink({ className, disabled = false, exclude, longPress, nativeContextMenu, ...props }, ref) {
32
+ return _jsx(ContextMenu.Trigger, { ...props, as: ContextLinkAnchor, className: classes('inkra-breadcrumb__context-link', className), disabled: disabled || props.loading, exclude: exclude, longPress: longPress, nativeContextMenu: nativeContextMenu, ref: ref });
33
+ });
34
+ const Page = forwardRef(function BreadcrumbPage({ className, children, endIcon, startIcon, ...props }, ref) {
35
+ const icon = (side, value) => value == null ? null : _jsx("span", { "aria-hidden": "true", className: `inkra-breadcrumb__icon inkra-breadcrumb__icon--${side}`, children: value });
36
+ return _jsxs("span", { ...props, ref: ref, "aria-current": "page", className: classes('inkra-breadcrumb__page', className), children: [icon('start', startIcon), _jsx("span", { className: "inkra-breadcrumb__label", children: copy(children) }), icon('end', endIcon)] });
21
37
  });
22
38
  const Separator = forwardRef(function BreadcrumbSeparator({ className, children = '/', ...props }, ref) {
23
39
  return _jsx("li", { ...props, ref: ref, role: "presentation", "aria-hidden": "true", className: classes('inkra-breadcrumb__separator', className), children: copy(children) });
24
40
  });
25
- export const Breadcrumb = { Root, List, Item, Link, Page, Separator };
41
+ export const Breadcrumb = { Root, List, Item, Link, MenuTrigger, ContextLink, Page, Separator };
@@ -1,4 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useInkraLocale, resolveInkraLocale } from '../i18n/index.js';
3
+ import { calendarLocales } from './locales.js';
2
4
  import { createContext, useContext, useMemo, useState } from 'react';
3
5
  import { CaptionLabel, Day, DayButton, DayPicker, Weekday } from 'react-day-picker';
4
6
  import { Button } from '../Button.js';
@@ -25,6 +27,7 @@ function CalendarChevron({ className, orientation = 'left', size = 28 }) {
25
27
  }
26
28
  // The trip calendar uses a full-cell date target with sibling event buttons.
27
29
  function CalendarCell({ children, day, modifiers, ...props }) {
30
+ const { t } = useInkraLocale();
28
31
  const { eventsByDay, appearance, onEventSelect, renderDayAccessory, maxEventsPerDay, showDayEventsLabel } = useContext(EventContext);
29
32
  const [expandedDay, setExpandedDay] = useState();
30
33
  if (appearance === 'picker')
@@ -33,15 +36,18 @@ function CalendarCell({ children, day, modifiers, ...props }) {
33
36
  const limit = maxEventsPerDay !== undefined && Number.isFinite(maxEventsPerDay) ? Math.max(0, Math.floor(maxEventsPerDay)) : entries.length;
34
37
  const expanded = expandedDay === dayKey(day.date);
35
38
  const shown = expanded || limit >= entries.length ? entries : entries.slice(0, limit);
36
- return _jsx(Day, { ...props, day: day, modifiers: modifiers, children: _jsxs("div", { className: "inkra-calendar__day-card", children: [_jsx(PencilSurface, { variant: modifiers.selected ? 'action' : 'quiet', radius: 8, className: "inkra-calendar__day-paper", "aria-hidden": "true", seed: `day-${day.date.toISOString()}` }), children, renderDayAccessory && _jsx("div", { className: "inkra-calendar__accessory", children: renderDayAccessory(day.date) }), _jsxs("div", { className: "inkra-calendar__events", children: [shown.map((event, index) => _jsx(PencilSurface, { as: "button", variant: "quiet", radius: 3, className: "inkra-calendar__event-card", disabled: modifiers.disabled || !onEventSelect, style: event.color ? { '--inkra-accent-soft': event.color } : undefined, "aria-label": `${event.label}${event.description ? `, ${event.description}` : ''}`, title: event.description ?? event.label, onClick: click => { click.stopPropagation(); onEventSelect?.(event); }, children: _jsx(PencilText, { ellipsis: true, children: event.label }) }, event.id ?? index)), entries.length > limit && _jsx(Button, { className: "inkra-calendar__events-toggle", size: "sm", variant: "ghost", "aria-expanded": expanded, onClick: click => { click.stopPropagation(); setExpandedDay(expanded ? undefined : dayKey(day.date)); }, children: showDayEventsLabel?.(day.date, entries.length, expanded) ?? (expanded ? 'Show fewer events' : `Show all ${entries.length} events`) })] })] }) });
39
+ return _jsx(Day, { ...props, day: day, modifiers: modifiers, children: _jsxs("div", { className: "inkra-calendar__day-card", children: [_jsx(PencilSurface, { variant: modifiers.selected ? 'action' : 'quiet', radius: 8, className: "inkra-calendar__day-paper", "aria-hidden": "true", seed: `day-${day.date.toISOString()}` }), children, renderDayAccessory && _jsx("div", { className: "inkra-calendar__accessory", children: renderDayAccessory(day.date) }), _jsxs("div", { className: "inkra-calendar__events", children: [shown.map((event, index) => _jsx(PencilSurface, { as: "button", variant: "quiet", radius: 3, className: "inkra-calendar__event-card", disabled: modifiers.disabled || !onEventSelect, style: event.color ? { '--inkra-accent-soft': event.color } : undefined, "aria-label": `${event.label}${event.description ? `, ${event.description}` : ''}`, title: event.description ?? event.label, onClick: click => { click.stopPropagation(); onEventSelect?.(event); }, children: _jsx(PencilText, { ellipsis: true, children: event.label }) }, event.id ?? index)), entries.length > limit && _jsx(Button, { className: "inkra-calendar__events-toggle", size: "sm", variant: "ghost", "aria-expanded": expanded, onClick: click => { click.stopPropagation(); setExpandedDay(expanded ? undefined : dayKey(day.date)); }, children: showDayEventsLabel?.(day.date, entries.length, expanded) ?? (expanded ? t('agenda.fewer') : t('agenda.all', { count: entries.length })) })] })] }) });
37
40
  }
38
41
  function CalendarDay({ children, modifiers, day, ...props }) {
42
+ const { t } = useInkraLocale();
39
43
  const { eventsByDay, indicator, appearance, maxEventsPerDay, showDayEventsLabel } = useContext(EventContext);
40
44
  const events = eventsByDay.get(dayKey(day.date)) ?? emptyEvents;
41
45
  return _jsxs(DayButton, { ...props, day: day, modifiers: modifiers, "aria-label": [props['aria-label'], ...(maxEventsPerDay !== undefined && Number.isFinite(maxEventsPerDay) && events.length > Math.max(0, Math.floor(maxEventsPerDay))
42
- ? [showDayEventsLabel?.(day.date, events.length, false) ?? `${events.length} events`] : events.map(event => event.label))].filter(Boolean).join(', '), children: [modifiers.selected && appearance === 'picker' && _jsx(PencilSurface, { variant: "selection", className: "inkra-calendar__selection", "aria-hidden": "true" }), _jsx(PencilText, { children: String(children) }), appearance === 'picker' && events.length > 0 && indicator !== 'none' && _jsx("span", { className: "inkra-calendar__event", "data-indicator": indicator, "aria-hidden": "true", children: indicator === 'count' ? events.length : '\u2022' })] });
46
+ ? [showDayEventsLabel?.(day.date, events.length, false) ?? t('agenda.count', { count: events.length })] : events.map(event => event.label))].filter(Boolean).join(', '), children: [modifiers.selected && appearance === 'picker' && _jsx(PencilSurface, { variant: "selection", className: "inkra-calendar__selection", "aria-hidden": "true" }), _jsx(PencilText, { children: String(children) }), appearance === 'picker' && events.length > 0 && indicator !== 'none' && _jsx("span", { className: "inkra-calendar__event", "data-indicator": indicator, "aria-hidden": "true", children: indicator === 'count' ? events.length : '\u2022' })] });
43
47
  }
44
48
  export function Calendar({ events = emptyEvents, eventIndicator = 'count', appearance = 'itinerary', onEventSelect, renderDayAccessory, displayRange, maxEventsPerDay, showDayEventsLabel, className, components, showOutsideDays = true, ...props }) {
49
+ const { locale, t } = useInkraLocale(props.locale?.code);
50
+ const eventSummary = useMemo(() => showDayEventsLabel ?? ((_date, total, expanded) => t(expanded ? 'agenda.fewer' : 'agenda.all', { count: total })), [showDayEventsLabel, t]);
45
51
  const eventsByDay = useMemo(() => {
46
52
  const index = new Map();
47
53
  for (const event of events) {
@@ -54,7 +60,7 @@ export function Calendar({ events = emptyEvents, eventIndicator = 'count', appea
54
60
  }
55
61
  return index;
56
62
  }, [events]);
57
- const context = useMemo(() => ({ eventsByDay, indicator: eventIndicator, appearance, onEventSelect, renderDayAccessory, maxEventsPerDay, showDayEventsLabel }), [eventsByDay, eventIndicator, appearance, onEventSelect, renderDayAccessory, maxEventsPerDay, showDayEventsLabel]);
63
+ const context = useMemo(() => ({ eventsByDay, indicator: eventIndicator, appearance, onEventSelect, renderDayAccessory, maxEventsPerDay, showDayEventsLabel: eventSummary }), [eventsByDay, eventIndicator, appearance, onEventSelect, renderDayAccessory, maxEventsPerDay, eventSummary]);
58
64
  const hidden = displayRange ? [...(Array.isArray(props.hidden) ? props.hidden : props.hidden ? [props.hidden] : []), { before: displayRange.from }, { after: displayRange.to }] : props.hidden;
59
- return _jsx(EventContext.Provider, { value: context, children: _jsx("div", { className: "inkra-calendar-scroll", "data-appearance": appearance, "data-event-summary": maxEventsPerDay !== undefined && Number.isFinite(maxEventsPerDay) || undefined, children: _jsx(DayPicker, { ...props, hidden: hidden, "data-appearance": appearance, "data-week-numbers": props.showWeekNumber || undefined, showOutsideDays: showOutsideDays, className: ['inkra-calendar', className].filter(Boolean).join(' '), components: { CaptionLabel: CalendarCaption, Weekday: CalendarWeekday, Chevron: CalendarChevron, PreviousMonthButton: CalendarMonthButton, NextMonthButton: CalendarMonthButton, Day: CalendarCell, DayButton: CalendarDay, ...components } }) }) });
65
+ return _jsx(EventContext.Provider, { value: context, children: _jsx("div", { className: "inkra-calendar-scroll", "data-appearance": appearance, "data-event-summary": maxEventsPerDay !== undefined && Number.isFinite(maxEventsPerDay) || undefined, children: _jsx(DayPicker, { ...props, locale: props.locale ?? calendarLocales[resolveInkraLocale(locale)], labels: { labelPrevious: () => t('datePicker.previous'), labelNext: () => t('datePicker.next'), ...props.labels }, hidden: hidden, "data-appearance": appearance, "data-week-numbers": props.showWeekNumber || undefined, showOutsideDays: showOutsideDays, className: ['inkra-calendar', className].filter(Boolean).join(' '), components: { CaptionLabel: CalendarCaption, Weekday: CalendarWeekday, Chevron: CalendarChevron, PreviousMonthButton: CalendarMonthButton, NextMonthButton: CalendarMonthButton, Day: CalendarCell, DayButton: CalendarDay, ...components } }) }) });
60
66
  }
@@ -0,0 +1,7 @@
1
+ export declare const calendarLocales: {
2
+ en: import("react-day-picker/locale").DayPickerLocale;
3
+ 'zh-CN': import("react-day-picker/locale").DayPickerLocale;
4
+ ja: import("react-day-picker/locale").DayPickerLocale;
5
+ ko: import("react-day-picker/locale").DayPickerLocale;
6
+ fr: import("react-day-picker/locale").DayPickerLocale;
7
+ };
@@ -0,0 +1,2 @@
1
+ import { enUS, zhCN, ja, ko, fr } from 'react-day-picker/locale';
2
+ export const calendarLocales = { en: enUS, 'zh-CN': zhCN, ja, ko, fr };
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import Autoplay from 'embla-carousel-autoplay';
3
4
  import useEmblaCarousel from 'embla-carousel-react';
4
5
  import { Children, forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState, } from 'react';
@@ -40,7 +41,9 @@ function useReducedMotion() {
40
41
  }, []);
41
42
  return reduced;
42
43
  }
43
- export const Carousel = forwardRef(function Carousel({ 'aria-label': ariaLabel = 'Carousel', align = 'start', autoplay = false, children, className, controls = true, currentIndex, defaultIndex = 0, dir, disabled = false, dragFree = false, emptyState, gap = 16, labels, loop = false, onBlurCapture, onCurrentIndexChange, onFocusCapture, onKeyDown, onPointerEnter, onPointerLeave, pagination = true, role = 'region', style, tabIndex, visibleItems = { base: 1, sm: 2, md: 3 }, ...props }, forwardedRef) {
44
+ export const Carousel = forwardRef(function Carousel(inkraProps, forwardedRef) {
45
+ const { t } = useInkraLocale();
46
+ const { 'aria-label': ariaLabel = t('carousel.label'), align = 'start', autoplay = false, children, className, controls = true, currentIndex, defaultIndex = 0, dir, disabled = false, dragFree = false, emptyState, gap = 16, labels, loop = false, onBlurCapture, onCurrentIndexChange, onFocusCapture, onKeyDown, onPointerEnter, onPointerLeave, pagination = true, role = 'region', style, tabIndex, visibleItems = { base: 1, sm: 2, md: 3 }, ...props } = inkraProps;
44
47
  const generatedId = useId();
45
48
  const rootRef = useRef(null);
46
49
  const slideItems = Children.toArray(children);
@@ -288,10 +291,10 @@ export const Carousel = forwardRef(function Carousel({ 'aria-label': ariaLabel =
288
291
  '--inkra-carousel-visible-md': visible.md,
289
292
  '--inkra-carousel-visible-lg': visible.lg,
290
293
  };
291
- const slideLabel = labels?.slide ?? ((index, total) => `Slide ${index} of ${total}`);
292
- const pageLabel = labels?.page ?? ((index, total) => `Go to page ${index} of ${total}`);
293
- const statusLabel = labels?.status ?? ((index, total) => `Slide ${index} of ${total}`);
294
+ const slideLabel = labels?.slide ?? ((index, total) => t('carousel.slide', { index, total }));
295
+ const pageLabel = labels?.page ?? ((index, total) => t('carousel.page', { index, total }));
296
+ const statusLabel = labels?.status ?? ((index, total) => t('carousel.slide', { index, total }));
294
297
  const showNavigation = controls && count > 1;
295
298
  const showPagination = pagination && scrollSnaps.length > 1;
296
- return _jsxs("div", { ...props, "aria-label": ariaLabel, "aria-roledescription": "carousel", className: ['inkra-carousel', className].filter(Boolean).join(' '), "data-autoplay": autoplayEnabled || undefined, "data-disabled": disabled || undefined, "data-playing": playing || undefined, dir: resolvedDir, onBlurCapture: handleBlur, onFocusCapture: handleFocus, onKeyDown: handleKeyDown, onPointerEnter: handlePointerEnter, onPointerLeave: handlePointerLeave, ref: rootRef, role: role, style: rootStyle, tabIndex: !disabled && count > 1 ? (tabIndex ?? 0) : tabIndex, children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-carousel__focus-ring", radius: 8, seed: `${generatedId}-focus`, variant: "outline" }), count === 0 ? _jsx("div", { className: "inkra-carousel__empty", children: emptyState ?? labels?.empty ?? 'No items' }) : _jsxs(_Fragment, { children: [_jsx("div", { className: "inkra-carousel__viewport", ref: viewportRef, children: _jsx("div", { className: "inkra-carousel__container", children: slideItems.map((item, index) => _jsx("div", { "aria-label": slideLabel(index + 1, count), "aria-roledescription": "slide", className: "inkra-carousel__slide", "data-current": index === activeIndex || undefined, role: "group", children: item }, item.key ?? index)) }) }), (showNavigation || showPagination || autoplayEnabled) && _jsxs("div", { className: "inkra-carousel__toolbar", children: [showNavigation && _jsxs("div", { className: "inkra-carousel__navigation", children: [_jsx(Button, { "aria-label": labels?.previous ?? 'Previous slide', disabled: disabled || !canScrollPrevious, onClick: previous, size: "icon", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: resolvedDir === 'rtl' ? nextPaths : previousPaths, seed: `${generatedId}-previous`, size: 20, sourceSize: 24 }) }), _jsx(Button, { "aria-label": labels?.next ?? 'Next slide', disabled: disabled || !canScrollNext, onClick: next, size: "icon", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: resolvedDir === 'rtl' ? previousPaths : nextPaths, seed: `${generatedId}-next`, size: 20, sourceSize: 24 }) })] }), showPagination && _jsx("div", { "aria-label": labels?.pages ?? 'Carousel pages', className: "inkra-carousel__pagination", role: "group", children: scrollSnaps.map((_, index) => _jsxs("button", { "aria-current": index === selectedIndex ? 'true' : undefined, "aria-label": pageLabel(index + 1, scrollSnaps.length), className: "inkra-carousel__page", disabled: disabled, onClick: () => scrollTo(index), type: "button", children: [_jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-carousel__page-mark", radius: 6, seed: `${generatedId}-page-${index}`, variant: index === selectedIndex ? 'selection' : 'outline' }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-carousel__page-focus", radius: 6, seed: `${generatedId}-page-${index}-focus`, variant: "outline" })] }, index)) }), autoplayEnabled && _jsx(Button, { "aria-label": playing ? (labels?.pause ?? 'Pause autoplay') : (labels?.play ?? 'Play autoplay'), disabled: !autoplayAllowed, onClick: toggleAutoplay, onPointerDown: () => { autoplayControlPointerRef.current = true; }, size: "icon", variant: "ghost", children: _jsx(PencilIcon, { decorative: true, paths: playing ? pausePaths : playPaths, seed: `${generatedId}-autoplay`, size: 20, sourceSize: 24 }) })] })] }), count > 0 && _jsx("span", { "aria-atomic": "true", "aria-live": playing ? 'off' : 'polite', className: "inkra-carousel__status", children: statusLabel(activeIndex + 1, count) })] });
299
+ return _jsxs("div", { ...props, "aria-label": ariaLabel, "aria-roledescription": "carousel", className: ['inkra-carousel', className].filter(Boolean).join(' '), "data-autoplay": autoplayEnabled || undefined, "data-disabled": disabled || undefined, "data-playing": playing || undefined, dir: resolvedDir, onBlurCapture: handleBlur, onFocusCapture: handleFocus, onKeyDown: handleKeyDown, onPointerEnter: handlePointerEnter, onPointerLeave: handlePointerLeave, ref: rootRef, role: role, style: rootStyle, tabIndex: !disabled && count > 1 ? (tabIndex ?? 0) : tabIndex, children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-carousel__focus-ring", radius: 8, seed: `${generatedId}-focus`, variant: "outline" }), count === 0 ? _jsx("div", { className: "inkra-carousel__empty", children: emptyState ?? labels?.empty ?? t('common.noItems') }) : _jsxs(_Fragment, { children: [_jsx("div", { className: "inkra-carousel__viewport", ref: viewportRef, children: _jsx("div", { className: "inkra-carousel__container", children: slideItems.map((item, index) => _jsx("div", { "aria-label": slideLabel(index + 1, count), "aria-roledescription": "slide", className: "inkra-carousel__slide", "data-current": index === activeIndex || undefined, role: "group", children: item }, item.key ?? index)) }) }), (showNavigation || showPagination || autoplayEnabled) && _jsxs("div", { className: "inkra-carousel__toolbar", children: [showNavigation && _jsxs("div", { className: "inkra-carousel__navigation", children: [_jsx(Button, { "aria-label": labels?.previous ?? t('carousel.previous'), disabled: disabled || !canScrollPrevious, onClick: previous, size: "icon", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: resolvedDir === 'rtl' ? nextPaths : previousPaths, seed: `${generatedId}-previous`, size: 20, sourceSize: 24 }) }), _jsx(Button, { "aria-label": labels?.next ?? t('carousel.next'), disabled: disabled || !canScrollNext, onClick: next, size: "icon", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: resolvedDir === 'rtl' ? previousPaths : nextPaths, seed: `${generatedId}-next`, size: 20, sourceSize: 24 }) })] }), showPagination && _jsx("div", { "aria-label": labels?.pages ?? t('carousel.pages'), className: "inkra-carousel__pagination", role: "group", children: scrollSnaps.map((_, index) => _jsxs("button", { "aria-current": index === selectedIndex ? 'true' : undefined, "aria-label": pageLabel(index + 1, scrollSnaps.length), className: "inkra-carousel__page", disabled: disabled, onClick: () => scrollTo(index), type: "button", children: [_jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-carousel__page-mark", radius: 6, seed: `${generatedId}-page-${index}`, variant: index === selectedIndex ? 'selection' : 'outline' }), _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-carousel__page-focus", radius: 6, seed: `${generatedId}-page-${index}-focus`, variant: "outline" })] }, index)) }), autoplayEnabled && _jsx(Button, { "aria-label": playing ? (labels?.pause ?? t('carousel.pause')) : (labels?.play ?? t('carousel.play')), disabled: !autoplayAllowed, onClick: toggleAutoplay, onPointerDown: () => { autoplayControlPointerRef.current = true; }, size: "icon", variant: "ghost", children: _jsx(PencilIcon, { decorative: true, paths: playing ? pausePaths : playPaths, seed: `${generatedId}-autoplay`, size: 20, sourceSize: 24 }) })] })] }), count > 0 && _jsx("span", { "aria-atomic": "true", "aria-live": playing ? 'off' : 'polite', className: "inkra-carousel__status", children: statusLabel(activeIndex + 1, count) })] });
297
300
  });
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { Children, createContext, forwardRef, memo, isValidElement, useContext, useCallback, useMemo, useId, useImperativeHandle, useRef, useState, } from 'react';
3
4
  import { Button } from '../Button.js';
4
5
  import { PencilSurface } from '../PencilSurface.js';
@@ -27,7 +28,8 @@ function gridStyle(style, columns, minItemWidth) {
27
28
  };
28
29
  }
29
30
  export const ChoiceGrid = memo(forwardRef(function ChoiceGrid(props, forwardedRef) {
30
- const { 'aria-describedby': ariaDescribedBy, 'aria-errormessage': ariaErrorMessage, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, children, className, columns, description, disabled = false, error, form, label, minItemWidth = '13rem', name, pageSize, previousPageLabel = 'Previous page', nextPageLabel = 'Next page', pageLabel = (page, pages) => `${page} / ${pages}`, style, type = 'single', ...rest } = props;
31
+ const { t } = useInkraLocale();
32
+ const { 'aria-describedby': ariaDescribedBy, 'aria-errormessage': ariaErrorMessage, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, children, className, columns, description, disabled = false, error, form, label, minItemWidth = '13rem', name, pageSize, previousPageLabel = t('common.previousPage'), nextPageLabel = t('common.nextPage'), pageLabel = (page, pages) => `${page} / ${pages}`, style, type = 'single', ...rest } = props;
31
33
  const generatedId = useId();
32
34
  const descriptionId = description == null ? undefined : `inkra-choice-grid-${generatedId}-description`;
33
35
  const errorId = error == null ? undefined : `inkra-choice-grid-${generatedId}-error`;
@@ -1,3 +1,4 @@
1
+ import { type InkraTranslate } from '../i18n/index.js';
1
2
  import type { ReactNode } from 'react';
2
3
  import type { ColorGradient } from './gradient.js';
3
4
  import type { ColorValue } from './color.js';
@@ -28,7 +29,7 @@ export interface ColorGradientLabels {
28
29
  farthestCorner?: string;
29
30
  repeating?: string;
30
31
  }
31
- export declare const defaultGradientLabels: Required<ColorGradientLabels>;
32
+ export declare const defaultGradientLabels: (t: InkraTranslate) => Required<ColorGradientLabels>;
32
33
  export declare function GradientEditor({ value, disabled, maxStops, labels, onChange, renderColorEditor }: {
33
34
  value: ColorGradient;
34
35
  disabled: boolean;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import {} from '../i18n/index.js';
2
3
  import { useId, useRef, useState } from 'react';
3
4
  import { __iconData as addIcon } from 'lucide-react/dist/esm/icons/plus.mjs';
4
5
  import { __iconData as removeIcon } from 'lucide-react/dist/esm/icons/minus.mjs';
@@ -15,15 +16,15 @@ import { Slider } from '../slider/index.js';
15
16
  import { Switch } from '../selection/Switch.js';
16
17
  import { ToggleGroup } from '../toggle-group/index.js';
17
18
  import { formatColorPaint } from './gradient.js';
18
- export const defaultGradientLabels = {
19
- type: 'Color type', solid: 'Solid', linear: 'Linear', radial: 'Radial', conic: 'Conic',
20
- preview: 'Gradient preview', stops: 'Color stops', stop: 'Stop', stopColor: 'Stop color',
21
- addStop: 'Add stop', removeStop: 'Remove stop', reverse: 'Reverse gradient',
22
- position: 'Stop position', angle: 'Angle', centerX: 'Center X', centerY: 'Center Y',
23
- shape: 'Shape', circle: 'Circle', ellipse: 'Ellipse', extent: 'Extent',
24
- closestSide: 'Closest side', closestCorner: 'Closest corner',
25
- farthestSide: 'Farthest side', farthestCorner: 'Farthest corner', repeating: 'Repeat gradient',
26
- };
19
+ export const defaultGradientLabels = (t) => ({
20
+ type: t('gradient.type'), solid: t('gradient.solid'), linear: t('gradient.linear'), radial: t('gradient.radial'), conic: t('gradient.conic'),
21
+ preview: t('gradient.preview'), stops: t('gradient.stops'), stop: t('gradient.stop'), stopColor: t('gradient.stopColor'),
22
+ addStop: t('gradient.add'), removeStop: t('gradient.remove'), reverse: t('gradient.reverse'),
23
+ position: t('gradient.position'), angle: t('gradient.angle'), centerX: t('gradient.centerX'), centerY: t('gradient.centerY'),
24
+ shape: t('gradient.shape'), circle: t('gradient.circle'), ellipse: t('gradient.ellipse'), extent: t('gradient.extent'),
25
+ closestSide: t('gradient.closestSide'), closestCorner: t('gradient.closestCorner'),
26
+ farthestSide: t('gradient.farthestSide'), farthestCorner: t('gradient.farthestCorner'), repeating: t('gradient.repeat'),
27
+ });
27
28
  const iconPaths = (data) => data.node.filter(([tag]) => tag === 'path').map(([, attrs]) => ({ d: attrs.d }));
28
29
  const actions = { add: iconPaths(addIcon), remove: iconPaths(removeIcon), reverse: iconPaths(reverseIcon) };
29
30
  export function GradientEditor({ value, disabled, maxStops, labels, onChange, renderColorEditor }) {
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useInkraLocale } from '../i18n/index.js';
2
3
  import { useEffect, useId, useMemo, useRef, useState } from 'react';
3
4
  import { Button } from '../Button.js';
4
5
  import { ColorSwatch } from '../color-swatch/index.js';
@@ -12,13 +13,13 @@ import { ToggleGroup } from '../toggle-group/index.js';
12
13
  import { GradientEditor, defaultGradientLabels } from './GradientEditor.js';
13
14
  import { colorPaintTypes, createColorGradient, formatColorPaint, normalizeColorPaint } from './gradient.js';
14
15
  import { DEFAULT_COLOR_VALUE, colorAlphaPercent, colorRgbValue, normalizeColorValue, withColorAlpha, withColorRgb, } from './color.js';
15
- const defaultLabels = {
16
- palette: 'Open color palette',
17
- value: 'Color value',
18
- alpha: 'Opacity',
19
- presets: 'Color presets',
20
- invalid: 'Use #RGB, #RGBA, #RRGGBB, or #RRGGBBAA.',
21
- };
16
+ const defaultLabels = (t) => ({
17
+ palette: t('color.palette'),
18
+ value: t('color.value'),
19
+ alpha: t('color.alpha'),
20
+ presets: t('color.presets'),
21
+ invalid: t('color.invalid'),
22
+ });
22
23
  function presetData(preset, allowGradient) {
23
24
  const input = typeof preset === 'string' ? preset : preset.value;
24
25
  const value = typeof input === 'string' || allowGradient ? normalizeColorPaint(input) : null;
@@ -27,7 +28,8 @@ function presetData(preset, allowGradient) {
27
28
  return { value, label: typeof preset === 'string' ? preset : preset.label ?? formatColorPaint(value) };
28
29
  }
29
30
  export function ColorPicker(options) {
30
- const { allowGradient = false, colorTypes = colorPaintTypes, maxStops = 8, gradientLabels, className, defaultValue = DEFAULT_COLOR_VALUE, disabled = false, form, id, label = 'Color', labels, name, onInvalidInput, onValueChange: _onValueChange, presets = [], presentation = 'inline', seed, showAlpha = true, value, ...props } = options;
31
+ const { t } = useInkraLocale();
32
+ const { allowGradient = false, colorTypes = colorPaintTypes, maxStops = 8, gradientLabels, className, defaultValue = DEFAULT_COLOR_VALUE, disabled = false, form, id, label = t('color.label'), labels, name, onInvalidInput, onValueChange: _onValueChange, presets = [], presentation = 'inline', seed, showAlpha = true, value, ...props } = options;
31
33
  const generatedId = useId();
32
34
  const resolvedId = id ?? `inkra-color-picker-${generatedId}`;
33
35
  const resolvedSeed = seed ?? resolvedId;
@@ -54,8 +56,8 @@ export function ColorPicker(options) {
54
56
  const skipNextBlurRef = useRef(false);
55
57
  const [draft, setDraft] = useState(currentColor);
56
58
  const [open, setOpen] = useState(false);
57
- const copy = { ...defaultLabels, ...labels };
58
- const gradientCopy = { ...defaultGradientLabels, ...gradientLabels };
59
+ const copy = { ...defaultLabels(t), ...labels };
60
+ const gradientCopy = { ...defaultGradientLabels(t), ...gradientLabels };
59
61
  const parsedPresets = useMemo(() => presets.map(preset => presetData(preset, allowGradient)).filter((preset) => preset !== null), [presets, allowGradient]);
60
62
  const inputInvalid = normalizeColorValue(draft) === null;
61
63
  const rgbValue = colorRgbValue(currentColor) ?? '#4b96aa';
@@ -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 * as AvatarPrimitive from '@radix-ui/react-avatar';
3
4
  import { createContext, useContext, forwardRef, useId, useState } from 'react';
4
5
  import { Button } from '../Button.js';
@@ -31,7 +32,9 @@ const CardFooter = forwardRef(function CardFooter({ className, children, divided
31
32
  return _jsxs("div", { ...props, ref: ref, "data-justify": justify, "data-padding": padding, "data-orientation": orientation, className: classes('inkra-card__footer', className), children: [(divided ?? inherited) && _jsx(PencilLine, { className: "inkra-card__divider" }), children] });
32
33
  });
33
34
  export const Card = { Root: CardRoot, Header: CardHeader, Title: CardTitle, Description: CardDescription, Content: CardContent, Footer: CardFooter };
34
- const AlertRoot = forwardRef(function AlertRoot({ className, variant = 'info', icon, seed, style, children, role = 'status', size = 'md', appearance = 'soft', actions, closable = false, open, defaultOpen = true, onOpenChange, closeLabel = 'Dismiss notification', ...props }, ref) {
35
+ const AlertRoot = forwardRef(function AlertRoot(inkraProps, ref) {
36
+ const { t } = useInkraLocale();
37
+ const { className, variant = 'info', icon, seed, style, children, role = 'status', size = 'md', appearance = 'soft', actions, closable = false, open, defaultOpen = true, onOpenChange, closeLabel = t('alert.dismiss'), ...props } = inkraProps;
35
38
  const id = useId();
36
39
  const [internalOpen, setInternalOpen] = useState(defaultOpen);
37
40
  if (!(open ?? internalOpen))
@@ -50,4 +50,4 @@ export interface DataTableProps<TData> extends Omit<HTMLAttributes<HTMLDivElemen
50
50
  pageLabel?: (page: number, pageCount: number) => ReactNode;
51
51
  sortStatusLabel?: (columnId: string, direction: false | 'asc' | 'desc') => string;
52
52
  }
53
- export declare function DataTable<TData>({ data, columns, getRowId, caption, density, layout, striped, hoverable, stickyHeader, captionSide, filterable, filterLabel, filterPlaceholder, globalFilter, defaultGlobalFilter, onGlobalFilterChange, sorting, defaultSorting, onSortingChange, pagination, manualPagination, rowCount, manualSorting, manualFiltering, paginationState, defaultPagination, onPaginationChange, selectable, rowSelection, defaultRowSelection, onRowSelectionChange, getRowCanSelect, loading, loadingMessage, emptyMessage, selectAllLabel, selectRowLabel, previousPageLabel, nextPageLabel, paginationLabel, pageLabel, sortStatusLabel, className, ...props }: DataTableProps<TData>): import("react").JSX.Element;
53
+ export declare function DataTable<TData>(inkraProps: DataTableProps<TData>): import("react").JSX.Element;
@@ -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 { flexRender, functionalUpdate, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table';
3
4
  import { useEffect, useMemo, useRef, useState } from 'react';
4
5
  import { Button } from '../Button.js';
@@ -28,7 +29,9 @@ function pencilCopy(value, seed) {
28
29
  function SelectionCheckbox(props) {
29
30
  return _jsx(Checkbox, { className: "inkra-data-table__selection", checked: props.checked, indeterminate: props.indeterminate, disabled: props.disabled, label: _jsx("span", { className: "inkra-visually-hidden", children: props.label }), onCheckedChange: props.onChange });
30
31
  }
31
- export function DataTable({ data, columns, getRowId, caption, density = 'comfortable', layout = 'auto', striped = false, hoverable = true, stickyHeader = false, captionSide = 'top', filterable = false, filterLabel = 'Filter rows', filterPlaceholder = 'Filter rows', globalFilter, defaultGlobalFilter = '', onGlobalFilterChange, sorting, defaultSorting = [], onSortingChange, pagination = false, manualPagination = false, rowCount, manualSorting = false, manualFiltering = false, paginationState, defaultPagination = { pageIndex: 0, pageSize: 10 }, onPaginationChange, selectable = false, rowSelection, defaultRowSelection = {}, onRowSelectionChange, getRowCanSelect, loading = false, loadingMessage = 'Loading rows', emptyMessage = 'No rows', selectAllLabel = 'Select all rows on this page', selectRowLabel = row => `Select row ${row.id}`, previousPageLabel = 'Previous page', nextPageLabel = 'Next page', paginationLabel = 'Table pagination', pageLabel = (page, pageCount) => `Page ${page} of ${pageCount}`, sortStatusLabel = (columnId, direction) => direction === 'asc' ? `${columnId}: sorted ascending` : direction === 'desc' ? `${columnId}: sorted descending` : `${columnId}: not sorted`, className, ...props }) {
32
+ export function DataTable(inkraProps) {
33
+ const { t } = useInkraLocale();
34
+ const { data, columns, getRowId, caption, density = 'comfortable', layout = 'auto', striped = false, hoverable = true, stickyHeader = false, captionSide = 'top', filterable = false, filterLabel = t('table.filter'), filterPlaceholder = t('table.filter'), globalFilter, defaultGlobalFilter = '', onGlobalFilterChange, sorting, defaultSorting = [], onSortingChange, pagination = false, manualPagination = false, rowCount, manualSorting = false, manualFiltering = false, paginationState, defaultPagination = { pageIndex: 0, pageSize: 10 }, onPaginationChange, selectable = false, rowSelection, defaultRowSelection = {}, onRowSelectionChange, getRowCanSelect, loading = false, loadingMessage = t('table.loading'), emptyMessage = t('table.empty'), selectAllLabel = t('table.selectAll'), selectRowLabel = row => t('table.selectRow', { row: row.id }), previousPageLabel = t('common.previousPage'), nextPageLabel = t('common.nextPage'), paginationLabel = t('table.pagination'), pageLabel = (page, pageCount) => t('pagination.status', { page, total: pageCount }), sortStatusLabel = (columnId, direction) => t(direction === 'asc' ? 'table.ascending' : direction === 'desc' ? 'table.descending' : 'table.unsorted', { column: columnId }), className, ...props } = inkraProps;
32
35
  const [internalFilter, setInternalFilter] = useState(defaultGlobalFilter);
33
36
  const [internalSorting, setInternalSorting] = useState(defaultSorting);
34
37
  const [internalPagination, setInternalPagination] = useState(defaultPagination);
@@ -1,4 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useInkraLocale, resolveInkraLocale } from '../i18n/index.js';
3
+ import { calendarLocales } from '../calendar/locales.js';
2
4
  import { forwardRef, useEffect, useId, useImperativeHandle, useMemo, useRef, useState, } from 'react';
3
5
  import { Button, ButtonGroup } from '../Button.js';
4
6
  import { Calendar } from '../calendar/index.js';
@@ -35,24 +37,19 @@ function initialForMode(mode, value) {
35
37
  return validDate(value) ? value : undefined;
36
38
  return isRange(value) ? value : undefined;
37
39
  }
38
- function defaultCopy(locale) {
39
- const chinese = locale.toLowerCase().startsWith('zh');
40
+ function defaultCopy(t) {
40
41
  return {
41
- calendar: chinese ? '选择日期' : 'Choose date',
42
- clear: chinese ? '清空日期' : 'Clear date',
43
- next: chinese ? '下个月' : 'Next month',
44
- open: chinese ? '打开日期选择器' : 'Open date picker',
45
- placeholder: chinese ? '选择日期' : 'Choose a date',
46
- previous: chinese ? '上个月' : 'Previous month',
47
- rangePlaceholder: chinese ? '选择日期范围' : 'Choose a date range',
48
- selected: chinese ? '已选择' : 'selected',
49
- today: chinese ? '今天' : 'today',
42
+ calendar: t('datePicker.calendar'), clear: t('datePicker.clear'),
43
+ next: t('datePicker.next'), open: t('datePicker.open'),
44
+ placeholder: t('datePicker.placeholder'), previous: t('datePicker.previous'),
45
+ rangePlaceholder: t('datePicker.range'), selected: t('datePicker.selected'), today: t('datePicker.today'),
50
46
  };
51
47
  }
52
48
  export const DatePicker = forwardRef(function DatePicker(allProps, forwardedRef) {
53
49
  const valueControlled = Object.prototype.hasOwnProperty.call(allProps, 'value');
54
50
  const openControlled = Object.prototype.hasOwnProperty.call(allProps, 'open');
55
- const { 'aria-describedby': ariaDescribedBy, 'aria-errormessage': ariaErrorMessage, 'aria-invalid': ariaInvalid, 'aria-label': ariaLabel, autoFocus, calendarLabel, calendarProps = {}, className, clearable = true, clearButtonLabel, defaultOpen = false, defaultValue, dir, disabled = false, endName, form, fullWidth = true, id, invalid = false, locale = 'en-US', mode = 'single', name, onBlur, onFocus, onOpenChange, onValueChange, open, openButtonLabel, placeholder, rangeSeparator = ' – ', readOnly = false, required = false, size = 'md', style, value, } = allProps;
51
+ const { 'aria-describedby': ariaDescribedBy, 'aria-errormessage': ariaErrorMessage, 'aria-invalid': ariaInvalid, 'aria-label': ariaLabel, autoFocus, calendarLabel, calendarProps = {}, className, clearable = true, clearButtonLabel, defaultOpen = false, defaultValue, dir, disabled = false, endName, form, fullWidth = true, id, invalid = false, locale: localeProp, mode = 'single', name, onBlur, onFocus, onOpenChange, onValueChange, open, openButtonLabel, placeholder, rangeSeparator = ' – ', readOnly = false, required = false, size = 'md', style, value, } = allProps;
52
+ const { locale, t } = useInkraLocale(localeProp);
56
53
  const generatedId = useId();
57
54
  const field = useFieldContext();
58
55
  const triggerRef = useRef(null);
@@ -71,7 +68,7 @@ export const DatePicker = forwardRef(function DatePicker(allProps, forwardedRef)
71
68
  'aria-errormessage': ariaErrorMessage,
72
69
  'aria-invalid': ariaInvalid,
73
70
  });
74
- const copy = defaultCopy(locale);
71
+ const copy = defaultCopy(t);
75
72
  const currentValue = initialForMode(mode, valueControlled ? value : uncontrolledValue);
76
73
  const currentOpen = openControlled ? Boolean(open) : uncontrolledOpen;
77
74
  const singleValue = validDate(currentValue) ? currentValue : undefined;
@@ -144,6 +141,7 @@ export const DatePicker = forwardRef(function DatePicker(allProps, forwardedRef)
144
141
  ...labels,
145
142
  };
146
143
  const sharedCalendarProps = {
144
+ locale: calendarLocales[resolveInkraLocale(locale)],
147
145
  ...restCalendarProps,
148
146
  appearance: 'picker',
149
147
  defaultMonth: defaultMonth ?? firstDate,
@@ -45,4 +45,4 @@ export declare function agendaInterval(event: AgendaEvent): {
45
45
  before: boolean;
46
46
  after: boolean;
47
47
  } | null;
48
- export declare function DayAgenda({ date, events, selectedEvent, onEventSelect, locale, timeZoneLabel, emptyLabel, untimedLabel, showOverview, hourCycle, density, showDescriptions, divided, disabled, onPreviousDay, onNextDay, navigationLabels, maxVisibleEvents, showAllEventsLabel, showFewerEventsLabel, className, ...props }: DayAgendaProps): import("react").JSX.Element;
48
+ export declare function DayAgenda(inkraProps: DayAgendaProps): import("react").JSX.Element;