@inkra/react 0.1.0 → 0.2.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 (160) hide show
  1. package/README.md +104 -1
  2. package/dist/PencilIcon.js +14 -16
  3. package/dist/PencilLine.js +18 -12
  4. package/dist/PencilText.js +192 -59
  5. package/dist/alert-dialog/index.d.ts +64 -0
  6. package/dist/alert-dialog/index.js +109 -0
  7. package/dist/audio-player/index.d.ts +60 -0
  8. package/dist/audio-player/index.js +101 -0
  9. package/dist/carousel/index.d.ts +51 -0
  10. package/dist/carousel/index.js +290 -0
  11. package/dist/choice-grid/index.d.ts +38 -0
  12. package/dist/choice-grid/index.js +85 -0
  13. package/dist/color-picker/color.d.ts +15 -0
  14. package/dist/color-picker/color.js +55 -0
  15. package/dist/color-picker/index.d.ts +31 -0
  16. package/dist/color-picker/index.js +69 -0
  17. package/dist/command/index.d.ts +23 -0
  18. package/dist/command/index.js +39 -0
  19. package/dist/context-menu/index.d.ts +76 -0
  20. package/dist/context-menu/index.js +240 -0
  21. package/dist/data-table/index.d.ts +47 -0
  22. package/dist/data-table/index.js +91 -0
  23. package/dist/date-picker/index.d.ts +50 -0
  24. package/dist/date-picker/index.js +180 -0
  25. package/dist/display/ProgressRing.d.ts +6 -0
  26. package/dist/display/ProgressRing.js +146 -0
  27. package/dist/display/index.d.ts +27 -0
  28. package/dist/display/index.js +18 -2
  29. package/dist/dockable-panel/index.d.ts +41 -0
  30. package/dist/dockable-panel/index.js +253 -0
  31. package/dist/dockable-panel/model.d.ts +21 -0
  32. package/dist/dockable-panel/model.js +61 -0
  33. package/dist/drawer/index.d.ts +63 -0
  34. package/dist/drawer/index.js +86 -0
  35. package/dist/editable/index.d.ts +53 -0
  36. package/dist/editable/index.js +162 -0
  37. package/dist/empty/index.d.ts +21 -0
  38. package/dist/empty/index.js +23 -0
  39. package/dist/flip/index.d.ts +49 -0
  40. package/dist/flip/index.js +138 -0
  41. package/dist/form/EditablePencilText.js +157 -146
  42. package/dist/form/Textarea.d.ts +12 -0
  43. package/dist/form/Textarea.js +83 -4
  44. package/dist/form/index.d.ts +1 -1
  45. package/dist/heatmap/index.d.ts +59 -0
  46. package/dist/heatmap/index.js +248 -0
  47. package/dist/hotkey/index.d.ts +37 -0
  48. package/dist/hotkey/index.js +130 -0
  49. package/dist/hotkey/model.d.ts +27 -0
  50. package/dist/hotkey/model.js +97 -0
  51. package/dist/hover-card/index.d.ts +33 -0
  52. package/dist/hover-card/index.js +141 -0
  53. package/dist/image-cropper/index.d.ts +82 -0
  54. package/dist/image-cropper/index.js +261 -0
  55. package/dist/image-cropper/model.d.ts +42 -0
  56. package/dist/image-cropper/model.js +94 -0
  57. package/dist/image-viewer/index.d.ts +79 -0
  58. package/dist/image-viewer/index.js +220 -0
  59. package/dist/image-viewer/model.d.ts +9 -0
  60. package/dist/image-viewer/model.js +18 -0
  61. package/dist/index.d.ts +47 -0
  62. package/dist/index.js +47 -0
  63. package/dist/internal/cache.d.ts +1 -1
  64. package/dist/internal/cache.js +1 -1
  65. package/dist/internal/renderTokens.d.ts +10 -0
  66. package/dist/internal/renderTokens.js +13 -0
  67. package/dist/internal/surfaceRenderer.d.ts +14 -6
  68. package/dist/internal/surfaceRenderer.js +15 -12
  69. package/dist/internal/textRenderer.d.ts +1 -0
  70. package/dist/internal/textRenderer.js +33 -7
  71. package/dist/internal/usePencilSurface.js +16 -8
  72. package/dist/internal/useSelectionDrawing.js +15 -11
  73. package/dist/journey-map/MapScale.js +3 -1
  74. package/dist/journey-map/index.js +3 -1
  75. package/dist/keypad/index.d.ts +23 -0
  76. package/dist/keypad/index.js +63 -0
  77. package/dist/keypad/model.d.ts +20 -0
  78. package/dist/keypad/model.js +33 -0
  79. package/dist/layout/index.d.ts +63 -0
  80. package/dist/layout/index.js +62 -0
  81. package/dist/log/index.d.ts +45 -0
  82. package/dist/log/index.js +159 -0
  83. package/dist/log/model.d.ts +16 -0
  84. package/dist/log/model.js +13 -0
  85. package/dist/node-graph/index.d.ts +123 -0
  86. package/dist/node-graph/index.js +304 -0
  87. package/dist/number-ticker/index.d.ts +29 -0
  88. package/dist/number-ticker/index.js +132 -0
  89. package/dist/number-ticker/model.d.ts +8 -0
  90. package/dist/number-ticker/model.js +34 -0
  91. package/dist/overflow-list/index.d.ts +39 -0
  92. package/dist/overflow-list/index.js +230 -0
  93. package/dist/overflow-list/layout.d.ts +19 -0
  94. package/dist/overflow-list/layout.js +44 -0
  95. package/dist/pagination/index.d.ts +23 -0
  96. package/dist/pagination/index.js +41 -0
  97. package/dist/pagination/model.d.ts +4 -0
  98. package/dist/pagination/model.js +42 -0
  99. package/dist/pan-zoom/index.d.ts +71 -0
  100. package/dist/pan-zoom/index.js +175 -0
  101. package/dist/radar/index.d.ts +54 -0
  102. package/dist/radar/index.js +220 -0
  103. package/dist/range-slider/index.d.ts +16 -0
  104. package/dist/range-slider/index.js +22 -0
  105. package/dist/rating/index.d.ts +28 -0
  106. package/dist/rating/index.js +127 -0
  107. package/dist/resizable/index.d.ts +32 -0
  108. package/dist/resizable/index.js +50 -0
  109. package/dist/scroll-area/index.d.ts +36 -0
  110. package/dist/scroll-area/index.js +44 -0
  111. package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
  112. package/dist/segmented-progress/SegmentedProgress.js +42 -0
  113. package/dist/slider/index.js +1 -1
  114. package/dist/slot/index.d.ts +24 -0
  115. package/dist/slot/index.js +29 -0
  116. package/dist/sortable/index.d.ts +50 -0
  117. package/dist/sortable/index.js +278 -0
  118. package/dist/sortable/model.d.ts +30 -0
  119. package/dist/sortable/model.js +64 -0
  120. package/dist/sparkline/index.d.ts +19 -0
  121. package/dist/sparkline/index.js +240 -0
  122. package/dist/sparkline/model.d.ts +16 -0
  123. package/dist/sparkline/model.js +28 -0
  124. package/dist/steps/index.d.ts +16 -0
  125. package/dist/steps/index.js +37 -0
  126. package/dist/styles.css +4850 -287
  127. package/dist/tag-input/index.d.ts +69 -0
  128. package/dist/tag-input/index.js +325 -0
  129. package/dist/tag-input/model.d.ts +14 -0
  130. package/dist/tag-input/model.js +38 -0
  131. package/dist/time-picker/index.d.ts +15 -0
  132. package/dist/time-picker/index.js +168 -0
  133. package/dist/timeline/index.d.ts +14 -0
  134. package/dist/timeline/index.js +16 -0
  135. package/dist/toast/index.d.ts +27 -0
  136. package/dist/toast/index.js +36 -0
  137. package/dist/toggle-group/index.d.ts +17 -0
  138. package/dist/toggle-group/index.js +26 -0
  139. package/dist/tokens.css +10 -9
  140. package/dist/transfer/index.d.ts +64 -0
  141. package/dist/transfer/index.js +174 -0
  142. package/dist/transfer/model.d.ts +12 -0
  143. package/dist/transfer/model.js +33 -0
  144. package/dist/tree/index.d.ts +39 -0
  145. package/dist/tree/index.js +36 -0
  146. package/dist/tree-select/index.d.ts +59 -0
  147. package/dist/tree-select/index.js +142 -0
  148. package/dist/tree-select/model.d.ts +31 -0
  149. package/dist/tree-select/model.js +135 -0
  150. package/dist/typewriter/index.d.ts +21 -0
  151. package/dist/typewriter/index.js +101 -0
  152. package/dist/typewriter/model.d.ts +7 -0
  153. package/dist/typewriter/model.js +45 -0
  154. package/dist/upload/index.d.ts +59 -0
  155. package/dist/upload/index.js +230 -0
  156. package/dist/virtual-list/index.d.ts +44 -0
  157. package/dist/virtual-list/index.js +128 -0
  158. package/dist/virtual-list/model.d.ts +7 -0
  159. package/dist/virtual-list/model.js +27 -0
  160. package/package.json +22 -3
@@ -0,0 +1,50 @@
1
+ import type { AriaAttributes, CSSProperties, FocusEventHandler } from 'react';
2
+ import type { DateRange } from 'react-day-picker';
3
+ import type { CalendarProps } from '../calendar/index.js';
4
+ export type DatePickerMode = 'single' | 'range';
5
+ export type DatePickerRange = DateRange;
6
+ export type DatePickerValue = Date | DatePickerRange | undefined;
7
+ export type DatePickerChangeReason = 'select' | 'clear';
8
+ export interface DatePickerChangeDetails {
9
+ reason: DatePickerChangeReason;
10
+ }
11
+ export type DatePickerCalendarProps = Omit<CalendarProps, 'appearance' | 'mode' | 'onSelect' | 'required' | 'selected'>;
12
+ export interface DatePickerProps {
13
+ 'aria-describedby'?: string;
14
+ 'aria-errormessage'?: string;
15
+ 'aria-invalid'?: AriaAttributes['aria-invalid'];
16
+ 'aria-label'?: string;
17
+ autoFocus?: boolean;
18
+ className?: string;
19
+ dir?: 'ltr' | 'rtl';
20
+ form?: string;
21
+ id?: string;
22
+ name?: string;
23
+ onBlur?: FocusEventHandler<HTMLButtonElement>;
24
+ onFocus?: FocusEventHandler<HTMLButtonElement>;
25
+ required?: boolean;
26
+ style?: CSSProperties;
27
+ mode?: DatePickerMode;
28
+ value?: DatePickerValue;
29
+ defaultValue?: DatePickerValue;
30
+ onValueChange?: (value: DatePickerValue, details: DatePickerChangeDetails) => void;
31
+ open?: boolean;
32
+ defaultOpen?: boolean;
33
+ onOpenChange?: (open: boolean) => void;
34
+ disabled?: boolean;
35
+ readOnly?: boolean;
36
+ invalid?: boolean;
37
+ locale?: string;
38
+ placeholder?: string;
39
+ rangeSeparator?: string;
40
+ endName?: string;
41
+ clearable?: boolean;
42
+ clearButtonLabel?: string;
43
+ calendarLabel?: string;
44
+ openButtonLabel?: string;
45
+ fullWidth?: boolean;
46
+ calendarProps?: DatePickerCalendarProps;
47
+ }
48
+ export declare function datePickerLocalValue(value: Date | undefined): string;
49
+ export declare const DatePicker: import("react").ForwardRefExoticComponent<DatePickerProps & import("react").RefAttributes<HTMLButtonElement>>;
50
+ export type DatePickerAriaInvalid = AriaAttributes['aria-invalid'];
@@ -0,0 +1,180 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useEffect, useId, useImperativeHandle, useMemo, useRef, useState, } from 'react';
3
+ import { Button } from '../Button.js';
4
+ import { Calendar } from '../calendar/index.js';
5
+ import { mergeAriaIds, useFieldContext } from '../form/context.js';
6
+ import { useResolvedControlProps } from '../form/controlProps.js';
7
+ import { PencilIcon } from '../PencilIcon.js';
8
+ import { PencilLine } from '../PencilLine.js';
9
+ import { Popover } from '../overlay/Popover.js';
10
+ const calendarPaths = [
11
+ { d: 'M8 2v4' },
12
+ { d: 'M16 2v4' },
13
+ { d: 'M3 10h18' },
14
+ { d: 'M5 4h14a2 2 0 0 1 2 2v14H3V6a2 2 0 0 1 2-2Z' },
15
+ ];
16
+ const clearPaths = [{ d: 'M18 6 6 18' }, { d: 'm6 6 12 12' }];
17
+ const classes = (...values) => values.filter(Boolean).join(' ');
18
+ function validDate(value) {
19
+ return value instanceof Date && Number.isFinite(value.getTime());
20
+ }
21
+ function isRange(value) {
22
+ return value != null && !validDate(value) && ('from' in value || 'to' in value);
23
+ }
24
+ export function datePickerLocalValue(value) {
25
+ if (!validDate(value))
26
+ return '';
27
+ return [
28
+ String(value.getFullYear()).padStart(4, '0'),
29
+ String(value.getMonth() + 1).padStart(2, '0'),
30
+ String(value.getDate()).padStart(2, '0'),
31
+ ].join('-');
32
+ }
33
+ function initialForMode(mode, value) {
34
+ if (mode === 'single')
35
+ return validDate(value) ? value : undefined;
36
+ return isRange(value) ? value : undefined;
37
+ }
38
+ function defaultCopy(locale) {
39
+ const chinese = locale.toLowerCase().startsWith('zh');
40
+ 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',
50
+ };
51
+ }
52
+ export const DatePicker = forwardRef(function DatePicker(allProps, forwardedRef) {
53
+ const valueControlled = Object.prototype.hasOwnProperty.call(allProps, 'value');
54
+ 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, style, value, } = allProps;
56
+ const generatedId = useId();
57
+ const field = useFieldContext();
58
+ const triggerRef = useRef(null);
59
+ const contentRef = useRef(null);
60
+ const [uncontrolledValue, setUncontrolledValue] = useState(() => initialForMode(mode, defaultValue));
61
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
62
+ const [constraintInvalid, setConstraintInvalid] = useState(false);
63
+ useImperativeHandle(forwardedRef, () => triggerRef.current);
64
+ const state = useResolvedControlProps({
65
+ id,
66
+ required,
67
+ disabled,
68
+ readOnly,
69
+ invalid,
70
+ 'aria-describedby': ariaDescribedBy,
71
+ 'aria-errormessage': ariaErrorMessage,
72
+ 'aria-invalid': ariaInvalid,
73
+ });
74
+ const copy = defaultCopy(locale);
75
+ const currentValue = initialForMode(mode, valueControlled ? value : uncontrolledValue);
76
+ const currentOpen = openControlled ? Boolean(open) : uncontrolledOpen;
77
+ const singleValue = validDate(currentValue) ? currentValue : undefined;
78
+ const rangeValue = isRange(currentValue) ? currentValue : undefined;
79
+ const firstDate = singleValue ?? rangeValue?.from ?? rangeValue?.to;
80
+ const hasValue = Boolean(singleValue || rangeValue?.from || rangeValue?.to);
81
+ const formatter = useMemo(() => new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }), [locale]);
82
+ const fullFormatter = useMemo(() => new Intl.DateTimeFormat(locale, {
83
+ day: 'numeric', month: 'long', weekday: 'long', year: 'numeric',
84
+ }), [locale]);
85
+ const captionFormatter = useMemo(() => new Intl.DateTimeFormat(locale, {
86
+ month: 'long', year: 'numeric',
87
+ }), [locale]);
88
+ const weekdayFormatter = useMemo(() => new Intl.DateTimeFormat(locale, { weekday: 'short' }), [locale]);
89
+ const displayValue = singleValue
90
+ ? formatter.format(singleValue)
91
+ : rangeValue?.from
92
+ ? `${formatter.format(rangeValue.from)}${rangeSeparator}${rangeValue.to ? formatter.format(rangeValue.to) : '…'}`
93
+ : placeholder ?? (mode === 'range' ? copy.rangePlaceholder : copy.placeholder);
94
+ const triggerId = state.id ?? `inkra-date-picker-${generatedId}`;
95
+ const accessibleValueId = field ? `${triggerId}-value` : undefined;
96
+ const updateOpen = (nextOpen) => {
97
+ if (!openControlled)
98
+ setUncontrolledOpen(nextOpen);
99
+ onOpenChange?.(nextOpen);
100
+ };
101
+ const commit = (nextValue, reason) => {
102
+ if (!valueControlled)
103
+ setUncontrolledValue(initialForMode(mode, nextValue));
104
+ setConstraintInvalid(false);
105
+ onValueChange?.(initialForMode(mode, nextValue), { reason });
106
+ };
107
+ useEffect(() => {
108
+ const trigger = triggerRef.current;
109
+ if (!trigger || valueControlled)
110
+ return;
111
+ let timer;
112
+ const reset = (event) => {
113
+ clearTimeout(timer);
114
+ timer = setTimeout(() => {
115
+ if (!event.defaultPrevented) {
116
+ setUncontrolledValue(initialForMode(mode, defaultValue));
117
+ setUncontrolledOpen(false);
118
+ setConstraintInvalid(false);
119
+ }
120
+ }, 0);
121
+ };
122
+ trigger.form?.addEventListener('reset', reset);
123
+ return () => {
124
+ clearTimeout(timer);
125
+ trigger.form?.removeEventListener('reset', reset);
126
+ };
127
+ }, [defaultValue, form, mode, valueControlled]);
128
+ const { disabled: disabledDays, formatters, labels, defaultMonth, ...restCalendarProps } = calendarProps;
129
+ const localizedFormatters = {
130
+ formatCaption: date => captionFormatter.format(date),
131
+ formatDay: date => new Intl.NumberFormat(locale).format(date.getDate()),
132
+ formatWeekdayName: date => weekdayFormatter.format(date),
133
+ ...formatters,
134
+ };
135
+ const localizedLabels = {
136
+ labelDayButton: (date, modifiers) => [
137
+ fullFormatter.format(date),
138
+ modifiers.today ? copy.today : '',
139
+ modifiers.selected ? copy.selected : '',
140
+ ].filter(Boolean).join(', '),
141
+ labelNext: () => copy.next,
142
+ labelPrevious: () => copy.previous,
143
+ ...labels,
144
+ };
145
+ const sharedCalendarProps = {
146
+ ...restCalendarProps,
147
+ appearance: 'picker',
148
+ defaultMonth: defaultMonth ?? firstDate,
149
+ disabled: state.readOnly ? true : disabledDays,
150
+ formatters: localizedFormatters,
151
+ labels: localizedLabels,
152
+ };
153
+ return (_jsxs(Popover.Root, { open: currentOpen, onOpenChange: updateOpen, children: [_jsxs("span", { className: classes('inkra-date-picker', className), "data-disabled": state.disabled || undefined, "data-full-width": fullWidth || undefined, "data-has-value": hasValue || undefined, "data-invalid": state.invalid || constraintInvalid || undefined, "data-mode": mode, "data-readonly": state.readOnly || undefined, dir: dir, style: style, children: [_jsx(Popover.Trigger, { asChild: true, children: _jsx(Button, { "aria-describedby": mergeAriaIds(state.describedBy, accessibleValueId), "aria-errormessage": state.errorMessage, "aria-invalid": constraintInvalid || state.ariaInvalid, "aria-label": ariaLabel ?? (openButtonLabel ? `${openButtonLabel}: ${displayValue}` : undefined), "aria-required": state.required || undefined, autoFocus: autoFocus, className: "inkra-date-picker__trigger", disabled: state.disabled, form: form, fullWidth: fullWidth, id: triggerId, onBlur: onBlur, onFocus: onFocus, ref: triggerRef, startIcon: _jsx(PencilIcon, { paths: calendarPaths, sourceSize: 24, size: 22 }), variant: "outline", children: _jsx("span", { className: "inkra-date-picker__value", "data-placeholder": !hasValue || undefined, children: displayValue }) }) }), accessibleValueId ? _jsx("span", { className: "inkra-date-picker__accessible-value", id: accessibleValueId, children: displayValue }) : null, clearable ? (_jsx(Button, { "aria-hidden": !hasValue || undefined, "aria-label": clearButtonLabel ?? copy.clear, className: "inkra-date-picker__clear", disabled: !hasValue || state.disabled || state.readOnly, onClick: () => {
154
+ commit(undefined, 'clear');
155
+ updateOpen(false);
156
+ triggerRef.current?.focus({ preventScroll: true });
157
+ }, size: "icon", tabIndex: hasValue ? 0 : -1, variant: "ghost", children: _jsx(PencilIcon, { paths: clearPaths, sourceSize: 24, size: 22 }) })) : null, name || state.required ? (mode === 'range' ? _jsxs(_Fragment, { children: [_jsx("input", { "aria-hidden": "true", className: "inkra-date-picker__form-field", disabled: state.disabled, form: form, name: name, onChange: () => undefined, onInvalid: event => { event.preventDefault(); setConstraintInvalid(true); triggerRef.current?.focus(); }, readOnly: state.readOnly, required: state.required, tabIndex: -1, type: "text", value: datePickerLocalValue(rangeValue?.from) }), _jsx("input", { "aria-hidden": "true", className: "inkra-date-picker__form-field", disabled: state.disabled, form: form, name: endName ?? (name ? `${name}End` : undefined), onChange: () => undefined, onInvalid: event => { event.preventDefault(); setConstraintInvalid(true); triggerRef.current?.focus(); }, readOnly: state.readOnly, required: state.required, tabIndex: -1, type: "text", value: datePickerLocalValue(rangeValue?.to) })] }) : _jsx("input", { "aria-hidden": "true", className: "inkra-date-picker__form-field", disabled: state.disabled, form: form, name: name, onChange: () => undefined, onInvalid: event => { event.preventDefault(); setConstraintInvalid(true); triggerRef.current?.focus(); }, readOnly: state.readOnly, required: state.required, tabIndex: -1, type: "text", value: datePickerLocalValue(singleValue) })) : null] }), _jsx(Popover.Portal, { children: _jsxs(Popover.Content, { align: "start", "aria-label": calendarLabel ?? copy.calendar, className: "inkra-date-picker__popover", collisionPadding: 12, onOpenAutoFocus: event => {
158
+ event.preventDefault();
159
+ requestAnimationFrame(() => {
160
+ requestAnimationFrame(() => {
161
+ const content = contentRef.current;
162
+ const selected = content?.querySelector('.rdp-selected .rdp-day_button:not(:disabled)');
163
+ const today = content?.querySelector('.rdp-today .rdp-day_button:not(:disabled)');
164
+ const available = content?.querySelector('.rdp-day_button:not(:disabled)');
165
+ (selected ?? today ?? available ?? content)?.focus({ preventScroll: true });
166
+ });
167
+ });
168
+ }, ref: contentRef, sideOffset: 8, children: [_jsx(PencilLine, { className: "inkra-date-picker__heading-line", seed: `date-picker-${generatedId}-line` }), mode === 'single' ? (_jsx(Calendar, { ...sharedCalendarProps, mode: "single", selected: singleValue, onSelect: nextValue => {
169
+ if (state.readOnly)
170
+ return;
171
+ commit(nextValue, 'select');
172
+ updateOpen(false);
173
+ } })) : (_jsx(Calendar, { ...sharedCalendarProps, excludeDisabled: true, mode: "range", selected: rangeValue, onSelect: nextValue => {
174
+ if (state.readOnly)
175
+ return;
176
+ commit(nextValue, 'select');
177
+ if (nextValue?.from && nextValue.to)
178
+ updateOpen(false);
179
+ } }))] }) })] }));
180
+ });
@@ -0,0 +1,6 @@
1
+ export declare function ProgressRing({ diameter, thickness, percent, seed }: {
2
+ diameter: number;
3
+ thickness: number;
4
+ percent: number | null;
5
+ seed: string | number;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,146 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef } from 'react';
3
+ import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost, canvasIsUsable, effectiveDpr } from '../internal/cache.js';
4
+ import { observeCanvasRecovery } from '../internal/recovery.js';
5
+ import { seedToNumber } from '../internal/random.js';
6
+ import { maxStrokeRoughness, pencilRenderDefaults, readRenderToken } from '../internal/renderTokens.js';
7
+ import { pencilStroke } from '../internal/stroke.js';
8
+ const ringCache = new BoundedCache(48, 8 * 1024 * 1024);
9
+ function smoothstep(value) {
10
+ return value * value * (3 - 2 * value);
11
+ }
12
+ function arcClip(diameter, radius, thickness, percent) {
13
+ const progress = Math.max(0, Math.min(100, percent ?? 28));
14
+ if (progress === 0)
15
+ return 'inset(50%)';
16
+ if (progress === 100)
17
+ return 'none';
18
+ const radians = progress / 100 * Math.PI * 2;
19
+ const arcLength = radius * radians;
20
+ const taperLength = Math.min(arcLength * 0.28, Math.max(7, thickness * 2.4));
21
+ const steps = Math.max(8, Math.ceil(progress * 0.9));
22
+ const outer = [];
23
+ const inner = [];
24
+ let start;
25
+ let end;
26
+ const point = (angle, distance) => `${((diameter / 2 + Math.cos(angle) * distance) / diameter * 100).toFixed(3)}% ${((diameter / 2 + Math.sin(angle) * distance) / diameter * 100).toFixed(3)}%`;
27
+ for (let index = 0; index <= steps; index += 1) {
28
+ const ratio = index / steps;
29
+ const distanceFromEnd = Math.min(ratio, 1 - ratio) * arcLength;
30
+ const taper = 0.12 + smoothstep(Math.min(1, distanceFromEnd / taperLength)) * 0.88;
31
+ // Leave enough room for the global stroke-width token while narrowing only
32
+ // the exposed ends. The pencil texture itself still determines the edge.
33
+ const halfWidth = thickness * 1.55 * taper;
34
+ const angle = ratio * radians - Math.PI / 2;
35
+ if (index === 0)
36
+ start = { angle, halfWidth };
37
+ if (index === steps)
38
+ end = { angle, halfWidth };
39
+ outer.push(point(angle, radius + halfWidth));
40
+ inner.push(point(angle, Math.max(0, radius - halfWidth)));
41
+ }
42
+ const endCap = Array.from({ length: 5 }, (_, index) => {
43
+ const capAngle = Math.PI / 2 - index / 4 * Math.PI;
44
+ const tangent = end.angle + Math.PI / 2;
45
+ const x = diameter / 2 + Math.cos(end.angle) * radius
46
+ + Math.cos(end.angle) * Math.sin(capAngle) * end.halfWidth
47
+ + Math.cos(tangent) * Math.cos(capAngle) * end.halfWidth;
48
+ const y = diameter / 2 + Math.sin(end.angle) * radius
49
+ + Math.sin(end.angle) * Math.sin(capAngle) * end.halfWidth
50
+ + Math.sin(tangent) * Math.cos(capAngle) * end.halfWidth;
51
+ return `${(x / diameter * 100).toFixed(3)}% ${(y / diameter * 100).toFixed(3)}%`;
52
+ });
53
+ const startCap = Array.from({ length: 5 }, (_, index) => {
54
+ const capAngle = -Math.PI / 2 + index / 4 * Math.PI;
55
+ const tangent = start.angle + Math.PI / 2;
56
+ const x = diameter / 2 + Math.cos(start.angle) * radius
57
+ + Math.cos(start.angle) * Math.sin(capAngle) * start.halfWidth
58
+ - Math.cos(tangent) * Math.cos(capAngle) * start.halfWidth;
59
+ const y = diameter / 2 + Math.sin(start.angle) * radius
60
+ + Math.sin(start.angle) * Math.sin(capAngle) * start.halfWidth
61
+ - Math.sin(tangent) * Math.cos(capAngle) * start.halfWidth;
62
+ return `${(x / diameter * 100).toFixed(3)}% ${(y / diameter * 100).toFixed(3)}%`;
63
+ });
64
+ return `polygon(${outer.join(',')},${endCap.join(',')},${inner.reverse().join(',')},${startCap.join(',')})`;
65
+ }
66
+ export function ProgressRing({ diameter, thickness, percent, seed }) {
67
+ const canvasRef = useRef(null);
68
+ const radius = diameter / 2 - thickness - 4;
69
+ const clipPath = useMemo(() => arcClip(diameter, radius, thickness, percent), [diameter, percent, radius, thickness]);
70
+ // Paint a stable full ring once; CSS reveals the current arc without repainting its texture.
71
+ useEffect(() => {
72
+ const canvas = canvasRef.current;
73
+ if (!canvas)
74
+ return;
75
+ let lastPaint = '';
76
+ const paint = () => {
77
+ try {
78
+ if (!CSS.supports('clip-path', 'polygon(0 0, 100% 0, 100% 100%)'))
79
+ return;
80
+ const width = canvas.clientWidth;
81
+ if (!width)
82
+ return;
83
+ const style = getComputedStyle(canvas);
84
+ const weight = readRenderToken(style, '--inkra-stroke-width', pencilRenderDefaults.strokeWidth, 0.3, 3);
85
+ const roughness = readRenderToken(style, '--inkra-stroke-amplitude', pencilRenderDefaults.strokeRoughness, 0, maxStrokeRoughness);
86
+ const dpr = effectiveDpr(2);
87
+ const key = JSON.stringify([PENCIL_RENDERER_VERSION, diameter, thickness, width, dpr, style.color, weight, roughness, seed]);
88
+ if (key === lastPaint && canvasIsUsable(canvas))
89
+ return;
90
+ let image = ringCache.get(key);
91
+ if (image && !canvasIsUsable(image)) {
92
+ ringCache.delete(key);
93
+ image = undefined;
94
+ }
95
+ if (!image) {
96
+ image = document.createElement('canvas');
97
+ image.width = image.height = Math.ceil(width * dpr);
98
+ const context = image.getContext('2d');
99
+ if (!context)
100
+ throw new Error('Canvas unavailable');
101
+ context.scale(image.width / diameter, image.height / diameter);
102
+ const points = Array.from({ length: 361 }, (_, index) => {
103
+ const angle = index * Math.PI / 180 - Math.PI / 2;
104
+ return [diameter / 2 + Math.cos(angle) * radius, diameter / 2 + Math.sin(angle) * radius];
105
+ });
106
+ pencilStroke(context, points, style.color, thickness * weight / 2.15, seedToNumber(seed), roughness * 0.55, 2, true, { variation: 0.86, breaks: 0.32, grain: 0.84, gain: 1.35, step: 0.55, edgeGrain: true });
107
+ ringCache.set(key, image, canvasByteCost(image));
108
+ }
109
+ const context = canvas.getContext('2d');
110
+ if (!context || context.isContextLost?.())
111
+ throw new Error('Canvas unavailable');
112
+ canvas.width = image.width;
113
+ canvas.height = image.height;
114
+ context.drawImage(image, 0, 0);
115
+ canvas.dataset.ready = 'true';
116
+ lastPaint = key;
117
+ }
118
+ catch {
119
+ delete canvas.dataset.ready;
120
+ lastPaint = '';
121
+ }
122
+ };
123
+ const lost = () => { delete canvas.dataset.ready; lastPaint = ''; };
124
+ canvas.addEventListener('contextlost', lost);
125
+ const observer = new MutationObserver(paint);
126
+ for (let node = canvas; node; node = node.parentElement) {
127
+ observer.observe(node, { attributes: true, attributeFilter: ['style', 'class', 'dir', 'data-theme', 'data-inkra-theme'] });
128
+ }
129
+ const resize = new ResizeObserver(paint);
130
+ resize.observe(canvas);
131
+ const scheme = matchMedia('(prefers-color-scheme: dark)');
132
+ scheme.addEventListener('change', paint);
133
+ window.addEventListener('resize', paint);
134
+ const stopRecovery = observeCanvasRecovery(paint);
135
+ paint();
136
+ return () => {
137
+ observer.disconnect();
138
+ resize.disconnect();
139
+ scheme.removeEventListener('change', paint);
140
+ window.removeEventListener('resize', paint);
141
+ canvas.removeEventListener('contextlost', lost);
142
+ stopRecovery();
143
+ };
144
+ }, [diameter, radius, seed, thickness]);
145
+ return _jsx("span", { className: "inkra-progress__ring", "aria-hidden": "true", children: _jsx("span", { className: "inkra-progress__ring-rotation", children: _jsxs("span", { className: "inkra-progress__ring-direction", children: [_jsx("canvas", { ref: canvasRef, className: "inkra-progress__ring-ink", style: { clipPath } }), _jsx("svg", { className: "inkra-progress__ring-fallback", viewBox: `0 0 ${diameter} ${diameter}`, focusable: "false", children: _jsx("circle", { cx: diameter / 2, cy: diameter / 2, r: radius, fill: "none", stroke: "currentColor", strokeWidth: thickness, pathLength: 100, strokeDasharray: "100", strokeDashoffset: 100 - (percent ?? 28), strokeLinecap: "round", transform: `rotate(-90 ${diameter / 2} ${diameter / 2})` }) })] }) }) });
146
+ }
@@ -1,4 +1,6 @@
1
1
  import type { HTMLAttributes, ReactNode } from 'react';
2
+ import { type ProgressPartialSegment, type ProgressThreshold } from '../segmented-progress/SegmentedProgress.js';
3
+ export type { ProgressPartialSegment, ProgressThreshold } from '../segmented-progress/SegmentedProgress.js';
2
4
  export type BadgeVariant = 'neutral' | 'accent' | 'success' | 'warning' | 'danger' | 'info';
3
5
  export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
4
6
  children: ReactNode;
@@ -22,11 +24,36 @@ export interface SkeletonProps extends Omit<HTMLAttributes<HTMLDivElement>, 'chi
22
24
  radius?: number;
23
25
  }
24
26
  export declare const Skeleton: import("react").ForwardRefExoticComponent<SkeletonProps & import("react").RefAttributes<HTMLDivElement>>;
27
+ export interface ProgressRenderState {
28
+ value: number | null;
29
+ max: number;
30
+ percent: number | null;
31
+ }
32
+ export type ProgressContentPosition = 'center' | 'top' | 'bottom' | 'start' | 'end';
25
33
  export interface ProgressProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
26
34
  value: number | null;
27
35
  max?: number;
28
36
  label?: ReactNode;
29
37
  seed?: string | number;
30
38
  size?: 'sm' | 'md' | 'lg';
39
+ shape?: 'linear' | 'circular' | 'segmented';
40
+ /** Circular diameter in CSS pixels, clamped to 32-512. */
41
+ diameter?: number;
42
+ /** Circular stroke width, clamped to 1-diameter/8. */
43
+ thickness?: number;
44
+ /** Segmented cell count, clamped to 1-64. */
45
+ segmentCount?: number;
46
+ /** Segmented gap in CSS pixels, clamped to 0-24. */
47
+ segmentGap?: number;
48
+ /** How the active partial cell is represented. */
49
+ partialSegment?: ProgressPartialSegment;
50
+ /** Pigment ranges expressed in the same value units as max. */
51
+ thresholds?: readonly ProgressThreshold[];
52
+ /** Per-cell pigment overrides by zero-based segment index. */
53
+ segmentColors?: readonly string[];
54
+ /** Defaults to center for circular, end otherwise. Center maps to end for non-circular shapes. */
55
+ contentPosition?: ProgressContentPosition;
56
+ /** Non-interactive content; render state contains normalized values. */
57
+ children?: ReactNode | ((state: ProgressRenderState) => ReactNode);
31
58
  }
32
59
  export declare const Progress: import("react").ForwardRefExoticComponent<ProgressProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -3,6 +3,8 @@ import { forwardRef, useId } from 'react';
3
3
  import { PencilLine } from '../PencilLine.js';
4
4
  import { PencilSurface } from '../PencilSurface.js';
5
5
  import { PencilText } from '../PencilText.js';
6
+ import { SegmentedProgress, } from '../segmented-progress/SegmentedProgress.js';
7
+ import { ProgressRing } from './ProgressRing.js';
6
8
  const badgeTokens = {
7
9
  neutral: { foreground: 'ink-muted', pigment: 'surface-muted' },
8
10
  accent: { foreground: 'accent-strong', pigment: 'accent-soft' },
@@ -41,7 +43,7 @@ function normalizeValue(value, max) {
41
43
  return 0;
42
44
  return Math.min(max, Math.max(0, value));
43
45
  }
44
- export const Progress = forwardRef(function Progress({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, label, max, seed, style, value, size = 'md', ...props }, ref) {
46
+ export const Progress = forwardRef(function Progress({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, label, max, seed, style, value, size = 'md', shape = 'linear', diameter, thickness, segmentCount, segmentGap, partialSegment = 'fractional', thresholds, segmentColors, contentPosition, children, ...props }, ref) {
45
47
  const generatedId = useId();
46
48
  const labelId = `inkra-progress-${generatedId}-label`;
47
49
  const resolvedMax = normalizeMax(max);
@@ -51,9 +53,23 @@ export const Progress = forwardRef(function Progress({ 'aria-label': ariaLabel,
51
53
  ? labelId
52
54
  : ariaLabelledBy;
53
55
  const resolvedSeed = seed ?? `inkra-progress-${generatedId}`;
56
+ const resolvedDiameter = typeof diameter === 'number' && Number.isFinite(diameter)
57
+ ? Math.max(32, Math.min(512, diameter)) : { sm: 64, md: 96, lg: 128 }[size];
58
+ const resolvedThickness = Math.min(resolvedDiameter / 8, Math.max(1, typeof thickness === 'number' && Number.isFinite(thickness) ? thickness : { sm: 4, md: 6, lg: 8 }[size]));
59
+ const resolvedSegmentCount = typeof segmentCount === 'number' && Number.isFinite(segmentCount)
60
+ ? Math.max(1, Math.min(64, Math.round(segmentCount))) : 10;
61
+ const resolvedSegmentGap = typeof segmentGap === 'number' && Number.isFinite(segmentGap)
62
+ ? Math.max(0, Math.min(24, segmentGap)) : { sm: 2, md: 3, lg: 4 }[size];
63
+ const position = contentPosition ?? (shape === 'circular' ? 'center' : 'end');
64
+ const content = typeof children === 'function' ? children({ value: resolvedValue, max: resolvedMax, percent }) : children;
54
65
  const resolvedStyle = {
55
66
  ...style,
56
67
  '--inkra-progress-value': `${percent ?? 36}%`,
68
+ ...(shape === 'circular' ? {
69
+ '--inkra-progress-angle': `${(percent ?? 28) * 3.6}deg`,
70
+ '--inkra-progress-diameter': `${resolvedDiameter}px`,
71
+ '--inkra-progress-content-inset': `${(resolvedThickness * 2 + 8) / resolvedDiameter * 100}%`,
72
+ } : {}),
57
73
  };
58
- return (_jsxs("div", { ...props, "aria-label": ariaLabel, "aria-labelledby": resolvedLabelledBy, "aria-valuemax": resolvedMax, "aria-valuemin": 0, "aria-valuenow": resolvedValue ?? undefined, className: ['inkra-progress', className].filter(Boolean).join(' '), "data-state": resolvedValue === null ? 'indeterminate' : 'determinate', "data-value": resolvedValue ?? undefined, "data-size": size, ref: ref, role: "progressbar", style: resolvedStyle, children: [label == null ? null : (typeof label === 'string' ? (_jsx(PencilText, { as: "span", className: "inkra-progress__label", id: labelId, seed: `${resolvedSeed}-label`, children: label })) : _jsx("span", { className: "inkra-progress__label", id: labelId, children: label })), _jsxs("span", { "aria-hidden": "true", className: "inkra-progress__track", children: [_jsx(PencilSurface, { as: "span", className: "inkra-progress__outline", radius: 6, seed: `${resolvedSeed}-outline`, variant: "outline" }), _jsx("span", { className: "inkra-progress__fill-clip", children: _jsx(PencilSurface, { as: "span", className: "inkra-progress__fill", radius: 6, seed: `${resolvedSeed}-fill`, variant: "selection" }) })] })] }));
74
+ return (_jsxs("div", { ...props, "aria-label": ariaLabel, "aria-labelledby": resolvedLabelledBy, "aria-valuemax": resolvedMax, "aria-valuemin": 0, "aria-valuenow": resolvedValue ?? undefined, className: ['inkra-progress', className].filter(Boolean).join(' '), "data-state": resolvedValue === null ? 'indeterminate' : 'determinate', "data-value": resolvedValue ?? undefined, "data-size": size, "data-shape": shape, ref: ref, role: "progressbar", style: resolvedStyle, children: [label == null ? null : (typeof label === 'string' ? (_jsx(PencilText, { as: "span", className: "inkra-progress__label", id: labelId, seed: `${resolvedSeed}-label`, children: label })) : _jsx("span", { className: "inkra-progress__label", id: labelId, children: label })), _jsxs("div", { className: "inkra-progress__body", "data-content-position": content == null ? undefined : shape !== 'circular' && position === 'center' ? 'end' : position, children: [shape === 'circular' ? (_jsx(ProgressRing, { diameter: resolvedDiameter, thickness: resolvedThickness, percent: percent, seed: `${resolvedSeed}-ring` })) : shape === 'segmented' ? (_jsx(SegmentedProgress, { count: resolvedSegmentCount, gap: resolvedSegmentGap, max: resolvedMax, partialSegment: partialSegment, percent: percent, seed: `${resolvedSeed}-segments`, segmentColors: segmentColors, thresholds: thresholds })) : (_jsxs("span", { "aria-hidden": "true", className: "inkra-progress__track", children: [_jsx(PencilSurface, { as: "span", className: "inkra-progress__outline", radius: 6, seed: `${resolvedSeed}-outline`, variant: "outline" }), _jsx("span", { className: "inkra-progress__fill-clip", children: _jsx(PencilSurface, { as: "span", className: "inkra-progress__fill", radius: 6, seed: `${resolvedSeed}-fill`, variant: "selection" }) })] })), content != null && _jsx("div", { className: "inkra-progress__content", children: typeof content === 'string' || typeof content === 'number' ? _jsx(PencilText, { children: String(content) }) : content })] })] }));
59
75
  });
@@ -0,0 +1,41 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
+ import type { DockablePanelPosition, DockablePanelSnapEdge } from './model.js';
3
+ export type { DockablePanelBounds, DockablePanelPlacement, DockablePanelPosition, DockablePanelSize, DockablePanelSnapEdge, } from './model.js';
4
+ export type DockablePanelPositionReason = 'drag' | 'keyboard' | 'reset' | 'viewport';
5
+ export interface DockablePanelPositionChangeDetails {
6
+ reason: DockablePanelPositionReason;
7
+ snappedEdges: readonly DockablePanelSnapEdge[];
8
+ }
9
+ export interface DockablePanelLabels {
10
+ move?: string;
11
+ collapse?: string;
12
+ expand?: string;
13
+ reset?: string;
14
+ keyboardInstructions?: string;
15
+ }
16
+ export interface DockablePanelWorkspaceProps extends HTMLAttributes<HTMLDivElement> {
17
+ activeId?: string | null;
18
+ defaultActiveId?: string | null;
19
+ onActiveChange?: (id: string) => void;
20
+ baseZIndex?: number;
21
+ boundaryPadding?: number;
22
+ snapThreshold?: number;
23
+ titleBarVisibility?: number;
24
+ }
25
+ export declare const DockablePanelWorkspace: import("react").ForwardRefExoticComponent<DockablePanelWorkspaceProps & import("react").RefAttributes<HTMLDivElement>>;
26
+ export interface DockablePanelProps extends Omit<HTMLAttributes<HTMLElement>, 'onChange' | 'title'> {
27
+ id: string;
28
+ title: ReactNode;
29
+ position?: DockablePanelPosition;
30
+ defaultPosition?: DockablePanelPosition;
31
+ onPositionChange?: (position: DockablePanelPosition, details: DockablePanelPositionChangeDetails) => void;
32
+ collapsed?: boolean;
33
+ defaultCollapsed?: boolean;
34
+ onCollapsedChange?: (collapsed: boolean) => void;
35
+ collapsible?: boolean;
36
+ movable?: boolean;
37
+ keyboardStep?: number;
38
+ headerActions?: ReactNode;
39
+ labels?: DockablePanelLabels;
40
+ }
41
+ export declare const DockablePanel: import("react").ForwardRefExoticComponent<DockablePanelProps & import("react").RefAttributes<HTMLElement>>;