@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.
- package/README.md +109 -26
- package/host-ui.d.ts +4 -4
- package/package.json +7 -3
- package/src/components/{AccountAvatar.tsx → base/AccountAvatar.tsx} +10 -10
- package/src/components/base/ChartLegend.tsx +34 -0
- package/src/components/base/CopyButton.tsx +53 -0
- package/src/components/base/DetailRow.tsx +17 -0
- package/src/components/{ExistingAttachmentTile.tsx → base/ExistingAttachmentTile.tsx} +2 -2
- package/src/components/base/FormErrorBanner.tsx +27 -0
- package/src/components/base/FormField.tsx +19 -0
- package/src/components/base/ImageCropper.tsx +275 -0
- package/src/components/base/KpiCard.tsx +125 -0
- package/src/components/base/ProgressBar.tsx +328 -0
- package/src/components/base/RadioCardGroup.tsx +146 -0
- package/src/components/base/SegmentedFilter.tsx +50 -0
- package/src/components/base/StatusPill.tsx +90 -0
- package/src/components/base/TagPill.tsx +24 -0
- package/src/components/base/Tooltip.tsx +64 -0
- package/src/components/{ClientPicker.tsx → composite/ClientPicker.tsx} +1 -1
- package/src/components/composite/FormActions.tsx +79 -0
- package/src/components/composite/LiveTimer.tsx +434 -0
- package/src/components/{MarkdownNotes.tsx → composite/MarkdownNotes.tsx} +1 -1
- package/src/components/{PaymentAccountPicker.tsx → composite/PaymentAccountPicker.tsx} +2 -2
- package/src/components/composite/SecretReveal.tsx +63 -0
- package/src/components/{VoucherPicker.tsx → composite/VoucherPicker.tsx} +1 -1
- package/src/index.ts +84 -27
- package/src/utils/INPUT_CLASS.ts +7 -0
- package/src/{lib → utils}/account-logo-url.ts +1 -1
- package/src/{lib → utils}/accounts-index.tsx +2 -2
- package/src/{lib → utils}/attachments.ts +3 -3
- package/src/utils/formatFullDate.ts +14 -0
- package/src/utils/formatPHP.ts +13 -0
- package/src/utils/formatShortDate.ts +17 -0
- /package/src/components/{AddAttachmentTile.tsx → base/AddAttachmentTile.tsx} +0 -0
- /package/src/components/{CameraCapture.tsx → base/CameraCapture.tsx} +0 -0
- /package/src/components/{MentionTextarea.tsx → composite/MentionTextarea.tsx} +0 -0
- /package/src/{lib → utils}/account-icons.ts +0 -0
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @kahitsan/ksui
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
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
|
|
26
|
+
export { default as CameraCapture } from "./components/base/CameraCapture";
|
|
29
27
|
|
|
30
|
-
export { default as
|
|
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 "./
|
|
53
|
-
export type { IconComponent, AccountIconSlug } from "./
|
|
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 {
|
|
110
|
+
export { attachmentUrl, isResolvableAttachment } from "./utils/attachments";
|
|
56
111
|
|
|
57
|
-
export {
|
|
112
|
+
export { useAccountsIndex, resolveAccount, resolveAccountName } from "./utils/accounts-index";
|
|
58
113
|
|
|
59
|
-
export {
|
|
60
|
-
export type { ExistingAttachment } from "./components/ExistingAttachmentTile";
|
|
114
|
+
export { INPUT_CLASS } from "./utils/INPUT_CLASS";
|
|
61
115
|
|
|
62
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|