klun-ui 0.1.1 → 0.1.2

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ All notable changes to **klun-ui** are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/), and the project adheres to
5
+ [Semantic Versioning](https://semver.org/) (pre-1.0: minor/patch bumps may carry
6
+ small breaking changes).
7
+
8
+ ## [0.1.2] — 2026-06-20
9
+
10
+ ### Added
11
+ - **`DateTimePicker`** — a single input that selects both a date and a time. It
12
+ opens a popover containing the month calendar plus a time selector, sharing one
13
+ `Date` (changing the date keeps the time and vice-versa). Supports `inline`,
14
+ `placeholder`, `format`, `align` and `disabled`, mirroring the other pickers.
15
+
16
+ ## [0.1.1] — 2026-06-20
17
+
18
+ ### Changed
19
+ - **`DatePicker` / `DateRangePicker` now default to an input trigger.** They
20
+ render a read-only input that opens the calendar in a popover, instead of an
21
+ always-visible inline calendar. New props on both:
22
+ - `inline` — render the bare calendar (the previous behaviour).
23
+ - `placeholder` — trigger text when nothing is selected.
24
+ - `format` — `Intl.DateTimeFormatOptions` for the trigger label.
25
+ - `align` — popover alignment (`"left"` | `"right"`).
26
+ - `disabled` — disable the trigger.
27
+ - Corrected the package `description` (6 accent themes; dropped the stale
28
+ component count).
29
+
30
+ ### Migration
31
+ - Replace `<DatePicker … />` used as an inline calendar with
32
+ `<DatePicker inline … />` to keep the old always-open behaviour. The default
33
+ (no `inline`) is now the input-triggered popover.
34
+
35
+ ## [0.1.0] — 2026-06-20
36
+
37
+ ### Added
38
+ - Initial public release: a themeable React + TypeScript component library and
39
+ design system — light/dark, multiple accent themes, a global radius axis,
40
+ `ConfigProvider`, i18n locales, and the full component set (layout, forms,
41
+ navigation, data display, feedback, overlays, charts).
42
+
43
+ [0.1.2]: https://www.npmjs.com/package/klun-ui/v/0.1.2
44
+ [0.1.1]: https://www.npmjs.com/package/klun-ui/v/0.1.1
45
+ [0.1.0]: https://www.npmjs.com/package/klun-ui/v/0.1.0
@@ -6890,8 +6890,264 @@ var DatePicker = react.forwardRef(
6890
6890
  }
6891
6891
  );
6892
6892
  DatePicker.displayName = "DatePicker";
6893
+ var pad = (n) => String(n).padStart(2, "0");
6894
+ var TimePicker = react.forwardRef(
6895
+ function TimePicker2({ value = "09:00", onChange, className, style, ...props }, ref) {
6896
+ const t = useLocale();
6897
+ const [h, m] = value.split(":").map(Number);
6898
+ const mer = h >= 12 ? "PM" : "AM";
6899
+ const h12 = (h + 11) % 12 + 1;
6900
+ const set = (nh, nm, nmer) => {
6901
+ let hh = nh % 12;
6902
+ if (nmer === "PM") hh += 12;
6903
+ onChange?.(`${pad(hh)}:${pad(nm)}`);
6904
+ };
6905
+ const [open, setOpen] = react.useState(null);
6906
+ const rootRef = react.useRef(null);
6907
+ const setRoot = (node) => {
6908
+ rootRef.current = node;
6909
+ if (typeof ref === "function") ref(node);
6910
+ else if (ref) ref.current = node;
6911
+ };
6912
+ react.useEffect(() => {
6913
+ if (!open) return;
6914
+ const onDown = (e) => {
6915
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(null);
6916
+ };
6917
+ document.addEventListener("mousedown", onDown);
6918
+ return () => document.removeEventListener("mousedown", onDown);
6919
+ }, [open]);
6920
+ const hourOpts = Array.from({ length: 12 }, (_, i) => ({
6921
+ value: String(i + 1),
6922
+ label: pad(i + 1)
6923
+ }));
6924
+ const minOpts = Array.from({ length: 60 }, (_, i) => ({
6925
+ value: String(i),
6926
+ label: pad(i)
6927
+ }));
6928
+ const merOpts = [
6929
+ { value: "AM", label: t.timePicker.am },
6930
+ { value: "PM", label: t.timePicker.pm }
6931
+ ];
6932
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6933
+ "div",
6934
+ {
6935
+ ref: setRoot,
6936
+ className: chunkTPGAXYFU_cjs.cx("klun-time-picker", className),
6937
+ style,
6938
+ ...props,
6939
+ children: [
6940
+ /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-time-line klun-time-picker__icon" }),
6941
+ /* @__PURE__ */ jsxRuntime.jsx(
6942
+ TimeColumn,
6943
+ {
6944
+ options: hourOpts,
6945
+ current: String(h12),
6946
+ open: open === "h",
6947
+ onOpen: () => setOpen("h"),
6948
+ onClose: () => setOpen(null),
6949
+ onPick: (v) => {
6950
+ set(Number(v), m, mer);
6951
+ setOpen(null);
6952
+ }
6953
+ }
6954
+ ),
6955
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-time-picker__sep", children: ":" }),
6956
+ /* @__PURE__ */ jsxRuntime.jsx(
6957
+ TimeColumn,
6958
+ {
6959
+ options: minOpts,
6960
+ current: String(m),
6961
+ open: open === "m",
6962
+ onOpen: () => setOpen("m"),
6963
+ onClose: () => setOpen(null),
6964
+ onPick: (v) => {
6965
+ set(h12, Number(v), mer);
6966
+ setOpen(null);
6967
+ }
6968
+ }
6969
+ ),
6970
+ /* @__PURE__ */ jsxRuntime.jsx(
6971
+ TimeColumn,
6972
+ {
6973
+ className: "klun-time-picker__col--meridiem",
6974
+ options: merOpts,
6975
+ current: mer,
6976
+ open: open === "mer",
6977
+ onOpen: () => setOpen("mer"),
6978
+ onClose: () => setOpen(null),
6979
+ onPick: (v) => {
6980
+ set(h12, m, v);
6981
+ setOpen(null);
6982
+ }
6983
+ }
6984
+ )
6985
+ ]
6986
+ }
6987
+ );
6988
+ }
6989
+ );
6990
+ function TimeColumn({
6991
+ options,
6992
+ current,
6993
+ open,
6994
+ onOpen,
6995
+ onClose,
6996
+ onPick,
6997
+ className
6998
+ }) {
6999
+ const colRef = react.useRef(null);
7000
+ const [active, setActive] = react.useState(-1);
7001
+ const selected = options.find((o) => o.value === current);
7002
+ const panel = () => colRef.current?.querySelector(".klun-select-listbox") ?? null;
7003
+ const scrollTo = (i, center) => {
7004
+ requestAnimationFrame(() => {
7005
+ const p = panel();
7006
+ const el = p?.children[i];
7007
+ if (!p || !el) return;
7008
+ if (center) {
7009
+ p.scrollTop = el.offsetTop - p.clientHeight / 2 + el.offsetHeight / 2;
7010
+ } else if (el.offsetTop < p.scrollTop) {
7011
+ p.scrollTop = el.offsetTop - 4;
7012
+ } else if (el.offsetTop + el.offsetHeight > p.scrollTop + p.clientHeight) {
7013
+ p.scrollTop = el.offsetTop + el.offsetHeight - p.clientHeight + 4;
7014
+ }
7015
+ });
7016
+ };
7017
+ react.useEffect(() => {
7018
+ if (!open) return;
7019
+ const idx = options.findIndex((o) => o.value === current);
7020
+ setActive(idx);
7021
+ scrollTo(idx >= 0 ? idx : 0, true);
7022
+ }, [open]);
7023
+ const step = (dir) => {
7024
+ if (!options.length) return;
7025
+ let i = active;
7026
+ for (let n = 0; n < options.length; n++) {
7027
+ i = (i + dir + options.length) % options.length;
7028
+ if (!options[i].disabled) break;
7029
+ }
7030
+ setActive(i);
7031
+ scrollTo(i, false);
7032
+ };
7033
+ const onKeyDown = (e) => {
7034
+ if (!open) {
7035
+ if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
7036
+ e.preventDefault();
7037
+ onOpen();
7038
+ }
7039
+ return;
7040
+ }
7041
+ switch (e.key) {
7042
+ case "ArrowDown":
7043
+ e.preventDefault();
7044
+ step(1);
7045
+ break;
7046
+ case "ArrowUp":
7047
+ e.preventDefault();
7048
+ step(-1);
7049
+ break;
7050
+ case "Enter":
7051
+ case " ":
7052
+ e.preventDefault();
7053
+ if (options[active]) onPick(options[active].value);
7054
+ break;
7055
+ case "Escape":
7056
+ e.preventDefault();
7057
+ onClose();
7058
+ break;
7059
+ case "Tab":
7060
+ onClose();
7061
+ break;
7062
+ }
7063
+ };
7064
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: colRef, className: chunkTPGAXYFU_cjs.cx("klun-time-picker__col", className), children: [
7065
+ /* @__PURE__ */ jsxRuntime.jsx(
7066
+ "button",
7067
+ {
7068
+ type: "button",
7069
+ className: "klun-time-picker__trigger",
7070
+ "aria-haspopup": "listbox",
7071
+ "aria-expanded": open,
7072
+ "data-open": open || void 0,
7073
+ onClick: () => open ? onClose() : onOpen(),
7074
+ onKeyDown,
7075
+ children: selected ? selected.label : ""
7076
+ }
7077
+ ),
7078
+ open ? /* @__PURE__ */ jsxRuntime.jsx(
7079
+ SelectListbox,
7080
+ {
7081
+ className: "klun-time-picker__listbox",
7082
+ options,
7083
+ current,
7084
+ active,
7085
+ onActivate: setActive,
7086
+ onPick: (o) => onPick(o.value)
7087
+ }
7088
+ ) : null
7089
+ ] });
7090
+ }
7091
+ TimePicker.displayName = "TimePicker";
7092
+ var pad2 = (n) => String(n).padStart(2, "0");
7093
+ var DEFAULT_FORMAT2 = {
7094
+ year: "numeric",
7095
+ month: "short",
7096
+ day: "numeric",
7097
+ hour: "2-digit",
7098
+ minute: "2-digit"
7099
+ };
7100
+ function DateTimePanel({ value, onChange }) {
7101
+ const hhmm = value ? `${pad2(value.getHours())}:${pad2(value.getMinutes())}` : "09:00";
7102
+ const setDate = (d) => {
7103
+ const next = new Date(d);
7104
+ if (value) next.setHours(value.getHours(), value.getMinutes(), 0, 0);
7105
+ else next.setHours(9, 0, 0, 0);
7106
+ onChange(next);
7107
+ };
7108
+ const setTime = (t) => {
7109
+ const [h, m] = t.split(":").map(Number);
7110
+ const next = value ? new Date(value) : /* @__PURE__ */ new Date();
7111
+ next.setHours(h, m, 0, 0);
7112
+ onChange(next);
7113
+ };
7114
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "klun-date-time-picker__panel", children: [
7115
+ /* @__PURE__ */ jsxRuntime.jsx(DatePicker, { inline: true, className: "klun-date-picker--bare", value, onChange: setDate }),
7116
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "klun-date-time-picker__time", children: /* @__PURE__ */ jsxRuntime.jsx(TimePicker, { value: hhmm, onChange: setTime }) })
7117
+ ] });
7118
+ }
7119
+ var DateTimePicker = react.forwardRef(
7120
+ function DateTimePicker2({ value, onChange, inline, placeholder, format = DEFAULT_FORMAT2, align = "left", disabled, className, style, ...props }, ref) {
7121
+ const date = value ? new Date(value) : null;
7122
+ if (inline) {
7123
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: chunkTPGAXYFU_cjs.cx("klun-date-time-picker", className), style, ...props, children: /* @__PURE__ */ jsxRuntime.jsx(DateTimePanel, { value: date, onChange: (d) => onChange?.(d) }) });
7124
+ }
7125
+ const label = date ? date.toLocaleString(void 0, format) : "";
7126
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: chunkTPGAXYFU_cjs.cx("klun-date-time-field", className), style, ...props, children: /* @__PURE__ */ jsxRuntime.jsx(
7127
+ Popover,
7128
+ {
7129
+ align,
7130
+ width: 300,
7131
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(
7132
+ Input,
7133
+ {
7134
+ readOnly: true,
7135
+ disabled,
7136
+ value: label,
7137
+ placeholder: placeholder ?? "Select date & time",
7138
+ leadingIcon: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-calendar-2-line" }),
7139
+ trailingIcon: /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-arrow-down-s-line" }),
7140
+ style: { width: "100%", cursor: "pointer" }
7141
+ }
7142
+ ),
7143
+ children: /* @__PURE__ */ jsxRuntime.jsx(DateTimePanel, { value: date, onChange: (d) => onChange?.(d) })
7144
+ }
7145
+ ) });
7146
+ }
7147
+ );
7148
+ DateTimePicker.displayName = "DateTimePicker";
6893
7149
  var norm = (d) => d && new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
6894
- var DEFAULT_FORMAT2 = { month: "short", day: "numeric" };
7150
+ var DEFAULT_FORMAT3 = { month: "short", day: "numeric" };
6895
7151
  var RangeCalendar = react.forwardRef(function RangeCalendar2({ start, end, onPick, className, style }, ref) {
6896
7152
  const { datePicker } = useLocale();
6897
7153
  const base = start ? new Date(start) : /* @__PURE__ */ new Date();
@@ -6950,7 +7206,7 @@ var RangeCalendar = react.forwardRef(function RangeCalendar2({ start, end, onPic
6950
7206
  ] });
6951
7207
  });
6952
7208
  var DateRangePicker = react.forwardRef(
6953
- function DateRangePicker2({ start, end, onChange, inline, placeholder, format = DEFAULT_FORMAT2, align = "left", disabled, className, style, ...props }, ref) {
7209
+ function DateRangePicker2({ start, end, onChange, inline, placeholder, format = DEFAULT_FORMAT3, align = "left", disabled, className, style, ...props }, ref) {
6954
7210
  if (inline) {
6955
7211
  return /* @__PURE__ */ jsxRuntime.jsx(
6956
7212
  RangeCalendar,
@@ -8241,205 +8497,6 @@ var Textarea = react.forwardRef(function Textarea2({ error = false, disabled = f
8241
8497
  );
8242
8498
  });
8243
8499
  Textarea.displayName = "Textarea";
8244
- var pad = (n) => String(n).padStart(2, "0");
8245
- var TimePicker = react.forwardRef(
8246
- function TimePicker2({ value = "09:00", onChange, className, style, ...props }, ref) {
8247
- const t = useLocale();
8248
- const [h, m] = value.split(":").map(Number);
8249
- const mer = h >= 12 ? "PM" : "AM";
8250
- const h12 = (h + 11) % 12 + 1;
8251
- const set = (nh, nm, nmer) => {
8252
- let hh = nh % 12;
8253
- if (nmer === "PM") hh += 12;
8254
- onChange?.(`${pad(hh)}:${pad(nm)}`);
8255
- };
8256
- const [open, setOpen] = react.useState(null);
8257
- const rootRef = react.useRef(null);
8258
- const setRoot = (node) => {
8259
- rootRef.current = node;
8260
- if (typeof ref === "function") ref(node);
8261
- else if (ref) ref.current = node;
8262
- };
8263
- react.useEffect(() => {
8264
- if (!open) return;
8265
- const onDown = (e) => {
8266
- if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(null);
8267
- };
8268
- document.addEventListener("mousedown", onDown);
8269
- return () => document.removeEventListener("mousedown", onDown);
8270
- }, [open]);
8271
- const hourOpts = Array.from({ length: 12 }, (_, i) => ({
8272
- value: String(i + 1),
8273
- label: pad(i + 1)
8274
- }));
8275
- const minOpts = Array.from({ length: 60 }, (_, i) => ({
8276
- value: String(i),
8277
- label: pad(i)
8278
- }));
8279
- const merOpts = [
8280
- { value: "AM", label: t.timePicker.am },
8281
- { value: "PM", label: t.timePicker.pm }
8282
- ];
8283
- return /* @__PURE__ */ jsxRuntime.jsxs(
8284
- "div",
8285
- {
8286
- ref: setRoot,
8287
- className: chunkTPGAXYFU_cjs.cx("klun-time-picker", className),
8288
- style,
8289
- ...props,
8290
- children: [
8291
- /* @__PURE__ */ jsxRuntime.jsx("i", { className: "ri-time-line klun-time-picker__icon" }),
8292
- /* @__PURE__ */ jsxRuntime.jsx(
8293
- TimeColumn,
8294
- {
8295
- options: hourOpts,
8296
- current: String(h12),
8297
- open: open === "h",
8298
- onOpen: () => setOpen("h"),
8299
- onClose: () => setOpen(null),
8300
- onPick: (v) => {
8301
- set(Number(v), m, mer);
8302
- setOpen(null);
8303
- }
8304
- }
8305
- ),
8306
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "klun-time-picker__sep", children: ":" }),
8307
- /* @__PURE__ */ jsxRuntime.jsx(
8308
- TimeColumn,
8309
- {
8310
- options: minOpts,
8311
- current: String(m),
8312
- open: open === "m",
8313
- onOpen: () => setOpen("m"),
8314
- onClose: () => setOpen(null),
8315
- onPick: (v) => {
8316
- set(h12, Number(v), mer);
8317
- setOpen(null);
8318
- }
8319
- }
8320
- ),
8321
- /* @__PURE__ */ jsxRuntime.jsx(
8322
- TimeColumn,
8323
- {
8324
- className: "klun-time-picker__col--meridiem",
8325
- options: merOpts,
8326
- current: mer,
8327
- open: open === "mer",
8328
- onOpen: () => setOpen("mer"),
8329
- onClose: () => setOpen(null),
8330
- onPick: (v) => {
8331
- set(h12, m, v);
8332
- setOpen(null);
8333
- }
8334
- }
8335
- )
8336
- ]
8337
- }
8338
- );
8339
- }
8340
- );
8341
- function TimeColumn({
8342
- options,
8343
- current,
8344
- open,
8345
- onOpen,
8346
- onClose,
8347
- onPick,
8348
- className
8349
- }) {
8350
- const colRef = react.useRef(null);
8351
- const [active, setActive] = react.useState(-1);
8352
- const selected = options.find((o) => o.value === current);
8353
- const panel = () => colRef.current?.querySelector(".klun-select-listbox") ?? null;
8354
- const scrollTo = (i, center) => {
8355
- requestAnimationFrame(() => {
8356
- const p = panel();
8357
- const el = p?.children[i];
8358
- if (!p || !el) return;
8359
- if (center) {
8360
- p.scrollTop = el.offsetTop - p.clientHeight / 2 + el.offsetHeight / 2;
8361
- } else if (el.offsetTop < p.scrollTop) {
8362
- p.scrollTop = el.offsetTop - 4;
8363
- } else if (el.offsetTop + el.offsetHeight > p.scrollTop + p.clientHeight) {
8364
- p.scrollTop = el.offsetTop + el.offsetHeight - p.clientHeight + 4;
8365
- }
8366
- });
8367
- };
8368
- react.useEffect(() => {
8369
- if (!open) return;
8370
- const idx = options.findIndex((o) => o.value === current);
8371
- setActive(idx);
8372
- scrollTo(idx >= 0 ? idx : 0, true);
8373
- }, [open]);
8374
- const step = (dir) => {
8375
- if (!options.length) return;
8376
- let i = active;
8377
- for (let n = 0; n < options.length; n++) {
8378
- i = (i + dir + options.length) % options.length;
8379
- if (!options[i].disabled) break;
8380
- }
8381
- setActive(i);
8382
- scrollTo(i, false);
8383
- };
8384
- const onKeyDown = (e) => {
8385
- if (!open) {
8386
- if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
8387
- e.preventDefault();
8388
- onOpen();
8389
- }
8390
- return;
8391
- }
8392
- switch (e.key) {
8393
- case "ArrowDown":
8394
- e.preventDefault();
8395
- step(1);
8396
- break;
8397
- case "ArrowUp":
8398
- e.preventDefault();
8399
- step(-1);
8400
- break;
8401
- case "Enter":
8402
- case " ":
8403
- e.preventDefault();
8404
- if (options[active]) onPick(options[active].value);
8405
- break;
8406
- case "Escape":
8407
- e.preventDefault();
8408
- onClose();
8409
- break;
8410
- case "Tab":
8411
- onClose();
8412
- break;
8413
- }
8414
- };
8415
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: colRef, className: chunkTPGAXYFU_cjs.cx("klun-time-picker__col", className), children: [
8416
- /* @__PURE__ */ jsxRuntime.jsx(
8417
- "button",
8418
- {
8419
- type: "button",
8420
- className: "klun-time-picker__trigger",
8421
- "aria-haspopup": "listbox",
8422
- "aria-expanded": open,
8423
- "data-open": open || void 0,
8424
- onClick: () => open ? onClose() : onOpen(),
8425
- onKeyDown,
8426
- children: selected ? selected.label : ""
8427
- }
8428
- ),
8429
- open ? /* @__PURE__ */ jsxRuntime.jsx(
8430
- SelectListbox,
8431
- {
8432
- className: "klun-time-picker__listbox",
8433
- options,
8434
- current,
8435
- active,
8436
- onActivate: setActive,
8437
- onPick: (o) => onPick(o.value)
8438
- }
8439
- ) : null
8440
- ] });
8441
- }
8442
- TimePicker.displayName = "TimePicker";
8443
8500
  var Card = react.forwardRef(function Card2({ variant = "stroke", padding, className, style, children, ...props }, ref) {
8444
8501
  return /* @__PURE__ */ jsxRuntime.jsx(
8445
8502
  "div",
@@ -10048,6 +10105,7 @@ exports.CopyButton = CopyButton;
10048
10105
  exports.CounterInput = CounterInput;
10049
10106
  exports.DatePicker = DatePicker;
10050
10107
  exports.DateRangePicker = DateRangePicker;
10108
+ exports.DateTimePicker = DateTimePicker;
10051
10109
  exports.Descriptions = Descriptions;
10052
10110
  exports.DigitInput = DigitInput;
10053
10111
  exports.Divider = Divider;
@@ -10153,5 +10211,5 @@ exports.useSize = useSize;
10153
10211
  exports.viVN = viVN;
10154
10212
  exports.zhCN = zhCN;
10155
10213
  exports.zhTW = zhTW;
10156
- //# sourceMappingURL=chunk-2MTDM5CL.cjs.map
10157
- //# sourceMappingURL=chunk-2MTDM5CL.cjs.map
10214
+ //# sourceMappingURL=chunk-OPAZ7GAU.cjs.map
10215
+ //# sourceMappingURL=chunk-OPAZ7GAU.cjs.map