@kahitsan/ksui 0.3.0 → 0.4.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 (37) hide show
  1. package/README.md +109 -26
  2. package/host-ui.d.ts +4 -4
  3. package/package.json +7 -3
  4. package/src/components/{AccountAvatar.tsx → base/AccountAvatar.tsx} +10 -10
  5. package/src/components/base/ChartLegend.tsx +34 -0
  6. package/src/components/base/CopyButton.tsx +53 -0
  7. package/src/components/base/DetailRow.tsx +17 -0
  8. package/src/components/{ExistingAttachmentTile.tsx → base/ExistingAttachmentTile.tsx} +2 -2
  9. package/src/components/base/FormErrorBanner.tsx +27 -0
  10. package/src/components/base/FormField.tsx +19 -0
  11. package/src/components/base/ImageCropper.tsx +275 -0
  12. package/src/components/base/KpiCard.tsx +125 -0
  13. package/src/components/base/ProgressBar.tsx +328 -0
  14. package/src/components/base/RadioCardGroup.tsx +146 -0
  15. package/src/components/base/SegmentedFilter.tsx +50 -0
  16. package/src/components/base/StatusPill.tsx +90 -0
  17. package/src/components/base/TagPill.tsx +24 -0
  18. package/src/components/base/Tooltip.tsx +64 -0
  19. package/src/components/{ClientPicker.tsx → composite/ClientPicker.tsx} +1 -1
  20. package/src/components/composite/FormActions.tsx +79 -0
  21. package/src/components/composite/LiveTimer.tsx +434 -0
  22. package/src/components/{MarkdownNotes.tsx → composite/MarkdownNotes.tsx} +1 -1
  23. package/src/components/{PaymentAccountPicker.tsx → composite/PaymentAccountPicker.tsx} +2 -2
  24. package/src/components/composite/SecretReveal.tsx +63 -0
  25. package/src/components/{VoucherPicker.tsx → composite/VoucherPicker.tsx} +1 -1
  26. package/src/index.ts +84 -27
  27. package/src/utils/INPUT_CLASS.ts +7 -0
  28. package/src/{lib → utils}/account-logo-url.ts +1 -1
  29. package/src/{lib → utils}/accounts-index.tsx +2 -2
  30. package/src/{lib → utils}/attachments.ts +3 -3
  31. package/src/utils/formatFullDate.ts +14 -0
  32. package/src/utils/formatPHP.ts +13 -0
  33. package/src/utils/formatShortDate.ts +17 -0
  34. /package/src/components/{AddAttachmentTile.tsx → base/AddAttachmentTile.tsx} +0 -0
  35. /package/src/components/{CameraCapture.tsx → base/CameraCapture.tsx} +0 -0
  36. /package/src/components/{MentionTextarea.tsx → composite/MentionTextarea.tsx} +0 -0
  37. /package/src/{lib → utils}/account-icons.ts +0 -0
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- // @kahitsan/ksui shared SolidJS UI components for KahitSan/Hilinga plugins.
1
+ // @kahitsan/ksui: shared SolidJS UI components for KahitSan/Hilinga plugins.
2
2
  //
3
3
  // These were copied byte-for-byte across plugins (counter, transactions, ...);
4
4
  // this package is the single canonical copy, PUBLISHED to GitHub Packages and
@@ -7,28 +7,25 @@
7
7
  // The package ships its source under a `solid` export condition (see
8
8
  // package.json), so the consumer's vite-plugin-solid compiles these components
9
9
  // while solid-js and `@kserp/host-ui` stay EXTERNALIZED to the host runtime
10
- // globals the plugin IIFE bundles them identically to a local copy: no runtime
10
+ // globals. The plugin IIFE bundles them identically to a local copy: no runtime
11
11
  // change, single Solid instance, host UI kit reused. The `@kserp/host-ui` ambient
12
12
  // type contract ships alongside (host-ui.d.ts, the `./host-ui` export).
13
+ //
14
+ // Components live under two folders by category:
15
+ // base/ a primitive that stands on its own. It uses only solid-js,
16
+ // lucide-solid, and/or the host kit via "@kserp/host-ui". It does
17
+ // not import another ksui component.
18
+ // composite/ a component that wraps a base or composes two or more components
19
+ // into a higher-level widget.
20
+ // See CONTRIBUTING.md for the placement rule when adding a new component.
13
21
 
14
- export { default as MentionTextarea } from "./components/MentionTextarea";
15
- export type { MentionTextareaProps } from "./components/MentionTextarea";
16
-
17
- export { default as MarkdownNotes } from "./components/MarkdownNotes";
18
- export type { MarkdownNotesProps } from "./components/MarkdownNotes";
19
-
20
- export { default as ClientPicker } from "./components/ClientPicker";
21
- export type { ClientOption } from "./components/ClientPicker";
22
-
23
- export { default as VoucherPicker, calculateDiscount } from "./components/VoucherPicker";
24
- export type { VoucherOption } from "./components/VoucherPicker";
25
-
26
- export { default as CameraCapture } from "./components/CameraCapture";
22
+ // ---------------------------------------------------------------------------
23
+ // Base components
24
+ // ---------------------------------------------------------------------------
27
25
 
28
- export { default as AddAttachmentTile } from "./components/AddAttachmentTile";
26
+ export { default as CameraCapture } from "./components/base/CameraCapture";
29
27
 
30
- export { default as PaymentAccountPicker } from "./components/PaymentAccountPicker";
31
- export type { PaymentAccountOption } from "./components/PaymentAccountPicker";
28
+ export { default as AddAttachmentTile } from "./components/base/AddAttachmentTile";
32
29
 
33
30
  // The canonical account + attachment widget set: AccountAvatar (logo / type-icon
34
31
  // glyph / initial-on-color fallback, with its initials + color + SVG helpers)
@@ -41,22 +38,82 @@ export {
41
38
  getInitials,
42
39
  getAvatarColor,
43
40
  buildInitialsSvg,
44
- } from "./components/AccountAvatar";
45
- export type { AvatarAccount } from "./components/AccountAvatar";
41
+ } from "./components/base/AccountAvatar";
42
+ export type { AvatarAccount } from "./components/base/AccountAvatar";
43
+
44
+ export { default as ExistingAttachmentTile } from "./components/base/ExistingAttachmentTile";
45
+ export type { ExistingAttachment } from "./components/base/ExistingAttachmentTile";
46
+
47
+ // Plugin consolidation: widgets that were duplicated across plugins
48
+ // (form fields, detail rows, tooltips, progress/legend display, image cropping)
49
+ // promoted into the single canonical package.
50
+ export { default as FormField } from "./components/base/FormField";
51
+ export { default as DetailRow } from "./components/base/DetailRow";
52
+ export { default as Tooltip } from "./components/base/Tooltip";
53
+ export { default as ImageCropper } from "./components/base/ImageCropper";
54
+ export { default as ProgressBar, type ProgressBarProps } from "./components/base/ProgressBar";
55
+ export { default as ChartLegend } from "./components/base/ChartLegend";
56
+
57
+ // Plugin reusability backlog: atoms migrated from per-plugin copies into the
58
+ // single canonical package.
59
+ export { default as StatusPill, type StatusTone } from "./components/base/StatusPill";
60
+ export { default as SegmentedFilter, type SegmentedFilterOption } from "./components/base/SegmentedFilter";
61
+ export { default as CopyButton } from "./components/base/CopyButton";
62
+ export { default as KpiCard, type KpiCardProps, type KpiTone } from "./components/base/KpiCard";
63
+ export { default as RadioCardGroup } from "./components/base/RadioCardGroup";
64
+ export { default as FormErrorBanner } from "./components/base/FormErrorBanner";
65
+ export { default as TagPill } from "./components/base/TagPill";
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Composite components
69
+ // ---------------------------------------------------------------------------
70
+
71
+ // PaymentAccountPicker composes AccountAvatar inside a searchable dropdown.
72
+ export { default as PaymentAccountPicker } from "./components/composite/PaymentAccountPicker";
73
+ export type { PaymentAccountOption } from "./components/composite/PaymentAccountPicker";
74
+
75
+ // LiveTimer wraps ProgressBar with timer state and elapsed-time display.
76
+ export { default as LiveTimer, type LiveTimerProps } from "./components/composite/LiveTimer";
77
+
78
+ // Plugin reusability backlog: composites migrated from per-plugin copies.
79
+ export { default as SecretReveal, type SecretRevealProps } from "./components/composite/SecretReveal";
80
+ export { default as FormActions, type FormActionsProps } from "./components/composite/FormActions";
81
+
82
+ // Higher-level widgets: the search-and-create pickers and the mention-aware
83
+ // editor that the team treats as composites.
84
+ export { default as MentionTextarea } from "./components/composite/MentionTextarea";
85
+ export type { MentionTextareaProps } from "./components/composite/MentionTextarea";
86
+
87
+ export { default as MarkdownNotes } from "./components/composite/MarkdownNotes";
88
+ export type { MarkdownNotesProps } from "./components/composite/MarkdownNotes";
89
+
90
+ export { default as ClientPicker } from "./components/composite/ClientPicker";
91
+ export type { ClientOption } from "./components/composite/ClientPicker";
92
+
93
+ export { default as VoucherPicker, calculateDiscount } from "./components/composite/VoucherPicker";
94
+ export type { VoucherOption } from "./components/composite/VoucherPicker";
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Utils (not components)
98
+ // ---------------------------------------------------------------------------
46
99
 
47
100
  export {
48
101
  getAccountIcon,
49
102
  getAccountTone,
50
103
  ACCOUNT_ICON_SLUGS,
51
104
  ACCOUNT_ICON_LABELS,
52
- } from "./lib/account-icons";
53
- export type { IconComponent, AccountIconSlug } from "./lib/account-icons";
105
+ } from "./utils/account-icons";
106
+ export type { IconComponent, AccountIconSlug } from "./utils/account-icons";
107
+
108
+ export { buildLogoSrc } from "./utils/account-logo-url";
54
109
 
55
- export { buildLogoSrc } from "./lib/account-logo-url";
110
+ export { attachmentUrl, isResolvableAttachment } from "./utils/attachments";
56
111
 
57
- export { attachmentUrl, isResolvableAttachment } from "./lib/attachments";
112
+ export { useAccountsIndex, resolveAccount, resolveAccountName } from "./utils/accounts-index";
58
113
 
59
- export { default as ExistingAttachmentTile } from "./components/ExistingAttachmentTile";
60
- export type { ExistingAttachment } from "./components/ExistingAttachmentTile";
114
+ export { INPUT_CLASS } from "./utils/INPUT_CLASS";
61
115
 
62
- export { useAccountsIndex, resolveAccount, resolveAccountName } from "./lib/accounts-index";
116
+ // Plugin reusability backlog: formatting helpers migrated from per-plugin copies.
117
+ export { formatPHP } from "./utils/formatPHP";
118
+ export { formatShortDate } from "./utils/formatShortDate";
119
+ export { formatFullDate } from "./utils/formatFullDate";
@@ -0,0 +1,7 @@
1
+ // Shared Tailwind class string for text inputs across plugin forms: a
2
+ // full-width rounded input with the dark zinc surface and the amber focus
3
+ // ring. Copied verbatim from the packages remote UI atoms so the shared kit
4
+ // owns the single source of truth. Pure string constant, no dependencies.
5
+
6
+ export const INPUT_CLASS =
7
+ "w-full rounded-lg border border-zinc-700 bg-zinc-800/50 px-3 py-2 text-sm text-zinc-200 focus:border-amber-500/50 focus:outline-none";
@@ -6,7 +6,7 @@
6
6
 
7
7
  export function buildLogoSrc(s3Link: string | null | undefined): string {
8
8
  // The value flows straight into <img src>, so only an http(s) URL is allowed
9
- // through a stored javascript:/data:/vbscript: scheme would be stored XSS.
9
+ // through. A stored javascript:/data:/vbscript: scheme would be stored XSS.
10
10
  if (s3Link && /^https?:/i.test(s3Link)) return s3Link;
11
11
  return "";
12
12
  }
@@ -12,12 +12,12 @@
12
12
  // (read from the host's useActiveWorkspace()). Because plugin-ui ships as source
13
13
  // compiled into each plugin's IIFE, the module-level singleton stays per-plugin.
14
14
  // Degrades gracefully: when the financial-accounts plugin isn't deployed the
15
- // fetch 404s/fails and the index stays empty resolveAccount() then falls back
15
+ // fetch 404s/fails and the index stays empty, so resolveAccount() then falls back
16
16
  // to the type-default glyph.
17
17
 
18
18
  import { createResource, type Resource } from "solid-js";
19
19
  import { useActiveWorkspace } from "@kserp/host-ui";
20
- import type { AvatarAccount } from "../components/AccountAvatar";
20
+ import type { AvatarAccount } from "../components/base/AccountAvatar";
21
21
 
22
22
  interface IndexShape {
23
23
  byId: Map<number | string, AvatarAccount>;
@@ -1,19 +1,19 @@
1
1
  // Attachment URL resolver for plugin remotes.
2
2
  //
3
3
  // Attachments are object-storage only: the upload route stores the public S3
4
- // URL in s3_link, which is the sole reference the legacy on-disk file_path
4
+ // URL in s3_link, which is the sole reference. The legacy on-disk file_path
5
5
  // column and the kernel /assets/ fallback are gone.
6
6
 
7
7
  export function attachmentUrl(s3Link: string | null | undefined): string {
8
8
  // The value flows straight into <a href> / <img src>, so only an http(s) URL
9
- // is allowed through a stored javascript:/data:/vbscript: scheme would be
9
+ // is allowed through. A stored javascript:/data:/vbscript: scheme would be
10
10
  // stored XSS. Every attachment has a valid https s3_link; anything else
11
11
  // yields an empty src and the render site shows an "unavailable" placeholder.
12
12
  if (s3Link && /^https?:/i.test(s3Link)) return s3Link;
13
13
  return "";
14
14
  }
15
15
 
16
- // Whether an attachment can actually be fetched true only for a safe http(s)
16
+ // Whether an attachment can actually be fetched, true only for a safe http(s)
17
17
  // s3_link. Render sites show an "unavailable" placeholder otherwise.
18
18
  export function isResolvableAttachment(s3Link: string | null | undefined): boolean {
19
19
  return !!s3Link && /^https?:/i.test(s3Link);
@@ -0,0 +1,14 @@
1
+ // Canonical date+time formatter for the en-PH locale.
2
+ //
3
+ // Pure helper, no rendering and no domain coupling. Renders a parsed timestamp
4
+ // as "Mon D, YYYY · h:mm AM" using the platform Intl/Date globals. Returns the
5
+ // em-dash placeholder on a null or empty input so render sites can drop it
6
+ // straight into a cell without a guard.
7
+
8
+ export function formatFullDate(stamp: string | null | undefined): string {
9
+ if (!stamp) return "—";
10
+ const d = new Date(stamp);
11
+ const date = d.toLocaleDateString("en-PH", { month: "short", day: "numeric", year: "numeric" });
12
+ const time = d.toLocaleTimeString("en-PH", { hour: "numeric", minute: "2-digit", hour12: true });
13
+ return `${date} · ${time}`;
14
+ }
@@ -0,0 +1,13 @@
1
+ // Philippine peso currency formatter.
2
+ //
3
+ // Pure (.ts, no JSX): the single PHP-currency formatter every plugin had
4
+ // copy-pasted as formatCurrency / formatPHP. Parses a string or number,
5
+ // returns an em-dash placeholder on null / undefined / NaN, otherwise the
6
+ // en-PH PHP-formatted string. Zero deps beyond the platform Intl global.
7
+
8
+ export function formatPHP(amount: string | number | null | undefined): string {
9
+ if (amount === null || amount === undefined) return "—";
10
+ const num = typeof amount === "string" ? parseFloat(amount) : amount;
11
+ if (Number.isNaN(num)) return "—";
12
+ return new Intl.NumberFormat("en-PH", { style: "currency", currency: "PHP" }).format(num);
13
+ }
@@ -0,0 +1,17 @@
1
+ // Short-date formatter for plugin remotes.
2
+ //
3
+ // Renders a YYYY-MM-DD (or full ISO) date string as the en-PH short form
4
+ // (e.g. "Jan 5, 2026"). Hilinga is an Asia/Manila product, so the date part is
5
+ // anchored at local midnight ("T00:00:00") to avoid the UTC drift that
6
+ // toISOString would introduce. Returns an em-dash placeholder for a missing
7
+ // value. Pure helper, no DOM, no fetch.
8
+
9
+ export function formatShortDate(dateStr: string | null | undefined): string {
10
+ if (!dateStr) return "—";
11
+ const datePart = dateStr.includes("T") ? dateStr.split("T")[0] : dateStr;
12
+ return new Date(datePart + "T00:00:00").toLocaleDateString("en-PH", {
13
+ year: "numeric",
14
+ month: "short",
15
+ day: "numeric",
16
+ });
17
+ }
File without changes