@hotstaq/admin-panel 0.3.18 → 0.4.1

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 (43) hide show
  1. package/assets/css/freelight-panel.css +174 -0
  2. package/build/WebExport.d.ts.map +1 -1
  3. package/build/WebExport.js +7 -1
  4. package/build/WebExport.js.map +1 -1
  5. package/build/components/admin-add-panel.d.ts +56 -0
  6. package/build/components/admin-add-panel.d.ts.map +1 -0
  7. package/build/components/admin-add-panel.js +177 -0
  8. package/build/components/admin-add-panel.js.map +1 -0
  9. package/build/components/admin-card-table.d.ts +55 -0
  10. package/build/components/admin-card-table.d.ts.map +1 -0
  11. package/build/components/admin-card-table.js +149 -0
  12. package/build/components/admin-card-table.js.map +1 -0
  13. package/build/components/admin-detail-page.d.ts +68 -0
  14. package/build/components/admin-detail-page.d.ts.map +1 -0
  15. package/build/components/admin-detail-page.js +247 -0
  16. package/build/components/admin-detail-page.js.map +1 -0
  17. package/build/components/admin-disclaimer.d.ts +21 -0
  18. package/build/components/admin-disclaimer.d.ts.map +1 -0
  19. package/build/components/admin-disclaimer.js +37 -0
  20. package/build/components/admin-disclaimer.js.map +1 -0
  21. package/build/components/admin-eyebrow-heading.d.ts +23 -0
  22. package/build/components/admin-eyebrow-heading.d.ts.map +1 -0
  23. package/build/components/admin-eyebrow-heading.js +40 -0
  24. package/build/components/admin-eyebrow-heading.js.map +1 -0
  25. package/build/components/admin-form-field.d.ts +69 -0
  26. package/build/components/admin-form-field.d.ts.map +1 -0
  27. package/build/components/admin-form-field.js +117 -0
  28. package/build/components/admin-form-field.js.map +1 -0
  29. package/build/index.d.ts +7 -1
  30. package/build/index.d.ts.map +1 -1
  31. package/build/index.js +16 -1
  32. package/build/index.js.map +1 -1
  33. package/build/tsconfig.tsbuildinfo +1 -1
  34. package/build-web/AdminPanelComponents.js +2 -2
  35. package/package.json +1 -1
  36. package/src/WebExport.ts +7 -1
  37. package/src/components/admin-add-panel.ts +210 -0
  38. package/src/components/admin-card-table.ts +183 -0
  39. package/src/components/admin-detail-page.ts +270 -0
  40. package/src/components/admin-disclaimer.ts +43 -0
  41. package/src/components/admin-eyebrow-heading.ts +50 -0
  42. package/src/components/admin-form-field.ts +166 -0
  43. package/src/index.ts +18 -2
@@ -0,0 +1,43 @@
1
+ import { HotStaq, Hot, HotAPI, HotComponent, HotComponentOutput } from "hotstaq";
2
+
3
+ /**
4
+ * Pinned info card — short headline + body text, indigo left border.
5
+ * Pattern lifted from the DonorTiers "No governance influence"
6
+ * disclaimer on /donorTiers. Useful for "no influence", "draft only",
7
+ * "internal use", etc. notes that belong above the main content.
8
+ *
9
+ * Usage:
10
+ * <admin-disclaimer hot-heading="Disclaimer">
11
+ * The body text of the disclaimer goes here.
12
+ * </admin-disclaimer>
13
+ */
14
+ export class AdminDisclaimer extends HotComponent
15
+ {
16
+ /** Heading (h2.h6). */
17
+ heading: string;
18
+ /** Accent color for the left border. */
19
+ accent: string;
20
+
21
+ constructor (copy: HotComponent | HotStaq, api: HotAPI)
22
+ {
23
+ super (copy, api);
24
+
25
+ this.tag = "admin-disclaimer";
26
+ this.heading = "";
27
+ this.accent = "#4f46e5";
28
+ }
29
+
30
+ output (): string | HotComponentOutput[]
31
+ {
32
+ const heading = this.heading ? `<h2 class="h6 mb-2" style="letter-spacing:0.02em;">${this.heading}</h2>` : "";
33
+ return `
34
+ <div class="card fl-disclaimer mb-3" style="border-left:4px solid ${this.accent};">
35
+ <div class="card-body">
36
+ ${heading}
37
+ <div class="mb-0 text-muted" style="font-size:0.9rem;line-height:1.45;">
38
+ ${this.inner || ""}
39
+ </div>
40
+ </div>
41
+ </div>`;
42
+ }
43
+ }
@@ -0,0 +1,50 @@
1
+ import { HotStaq, Hot, HotAPI, HotComponent, HotComponentOutput } from "hotstaq";
2
+
3
+ /**
4
+ * A page heading with a small uppercase "eyebrow" label above it.
5
+ * Pattern lifted from the /live page on Freelight — the "Live Campaign
6
+ * Page" label above the candidate name.
7
+ *
8
+ * Usage:
9
+ * <admin-eyebrow-heading hot-eyebrow="Campaign Project"
10
+ * hot-heading="Q4 Outreach Plan"></admin-eyebrow-heading>
11
+ */
12
+ export class AdminEyebrowHeading extends HotComponent
13
+ {
14
+ /** The small uppercase label rendered above the heading. */
15
+ eyebrow: string;
16
+ /** The h1 text. */
17
+ heading: string;
18
+ /** Optional muted subtitle rendered below the heading. */
19
+ subtitle: string;
20
+ /** "center" | "start" (default). */
21
+ align: string;
22
+
23
+ constructor (copy: HotComponent | HotStaq, api: HotAPI)
24
+ {
25
+ super (copy, api);
26
+
27
+ this.tag = "admin-eyebrow-heading";
28
+ this.eyebrow = "";
29
+ this.heading = "";
30
+ this.subtitle = "";
31
+ this.align = "start";
32
+ }
33
+
34
+ output (): string | HotComponentOutput[]
35
+ {
36
+ const alignClass = this.align === "center" ? "text-center" : "";
37
+ const eyebrow = this.eyebrow
38
+ ? `<div class="text-uppercase text-muted small mb-2" style="letter-spacing:0.08em;">${this.eyebrow}</div>`
39
+ : "";
40
+ const subtitle = this.subtitle
41
+ ? `<p class="text-muted mb-0">${this.subtitle}</p>`
42
+ : "";
43
+ return `
44
+ <div class="fl-eyebrow-heading mb-3 ${alignClass}">
45
+ ${eyebrow}
46
+ <h1 class="h3 mb-1">${this.heading || this.inner || ""}</h1>
47
+ ${subtitle}
48
+ </div>`;
49
+ }
50
+ }
@@ -0,0 +1,166 @@
1
+ import { HotStaq, Hot, HotAPI, HotComponent, HotComponentOutput } from "hotstaq";
2
+
3
+ /**
4
+ * A labeled form field. Wraps a single <input>, <select>, or <textarea>
5
+ * with a proper <label>, an optional required-marker, and helper text.
6
+ *
7
+ * Replaces the boilerplate of hand-rolling label + input + small help
8
+ * div across every Add/Edit form. Inspired by the Freelight pages
9
+ * (volunteers.hott, yard-signs.hott, members.hott) where every field
10
+ * already follows this pattern.
11
+ *
12
+ * Usage:
13
+ * <admin-form-field hot-field="name" hot-label="First name" hot-required="1"
14
+ * hot-placeholder="e.g. Jane" hot-autocomplete="given-name">
15
+ * </admin-form-field>
16
+ *
17
+ * <admin-form-field hot-field="status" hot-label="Status" hot-control="select"
18
+ * hot-options="active,suspended,invited"></admin-form-field>
19
+ *
20
+ * <admin-form-field hot-field="description" hot-label="Description"
21
+ * hot-control="textarea" hot-rows="3"
22
+ * hot-help="Shown on the public page."></admin-form-field>
23
+ */
24
+ export class AdminFormField extends HotComponent
25
+ {
26
+ /** The field name (used for hot-field and the label's `for` attribute). */
27
+ field: string;
28
+ /** The visible label text. */
29
+ label: string;
30
+ /** "1" / "true" to render the red required marker after the label. */
31
+ required: string;
32
+ /** input type when control is "input" (text, email, tel, number, date, etc). */
33
+ type: string;
34
+ /** Control kind: "input" (default) | "select" | "textarea" | "checkbox". */
35
+ control: string;
36
+ /** Placeholder text (ignored for type=date / control=select / checkbox). */
37
+ placeholder: string;
38
+ /** Autocomplete attribute value (given-name, email, tel, street-address, etc). */
39
+ autocomplete: string;
40
+ /** Small helper text shown below the input. */
41
+ help: string;
42
+ /** Comma-separated options when control="select". Each item can be "value:label" or just "value". */
43
+ options: string;
44
+ /** Rows for control="textarea". */
45
+ rows: string;
46
+ /** Minimum value for type=number. */
47
+ min: string;
48
+ /** Step for type=number. */
49
+ step: string;
50
+ /** Max length for input/textarea. */
51
+ maxlength: string;
52
+ /** Bootstrap column class for grid use, e.g. "col-md-6". Empty => no wrapper col. */
53
+ col: string;
54
+ /** Size: "sm" (default in the new theme) | "md". */
55
+ size: string;
56
+ /** Extra CSS classes for the input element itself. */
57
+ css_class: string;
58
+
59
+ constructor (copy: HotComponent | HotStaq, api: HotAPI)
60
+ {
61
+ super (copy, api);
62
+
63
+ this.tag = "admin-form-field";
64
+ this.field = "";
65
+ this.label = "";
66
+ this.required = "0";
67
+ this.type = "text";
68
+ this.control = "input";
69
+ this.placeholder = "";
70
+ this.autocomplete = "";
71
+ this.help = "";
72
+ this.options = "";
73
+ this.rows = "3";
74
+ this.min = "";
75
+ this.step = "";
76
+ this.maxlength = "";
77
+ this.col = "";
78
+ this.size = "sm";
79
+ this.css_class = "";
80
+ }
81
+
82
+ /**
83
+ * Field renders inside the surrounding flow — no modal-body-relocation
84
+ * trick like admin-text does. The new components are designed for
85
+ * detail pages and inline add panels, not modals.
86
+ */
87
+ onPostPlace (parentHtmlElement: HTMLElement, htmlElement: HTMLElement): HTMLElement
88
+ {
89
+ return (null);
90
+ }
91
+
92
+ private renderInput (id: string, sizeClass: string, classes: string): string
93
+ {
94
+ const minAttr = this.min ? ` min="${this.min}"` : "";
95
+ const stepAttr = this.step ? ` step="${this.step}"` : "";
96
+ const maxAttr = this.maxlength ? ` maxlength="${this.maxlength}"` : "";
97
+ const acAttr = this.autocomplete ? ` autocomplete="${this.autocomplete}"` : "";
98
+ const phAttr = (this.type === "date" || this.placeholder === "")
99
+ ? "" : ` placeholder="${this.placeholder.replace (/"/g, "&quot;")}"`;
100
+ return `<input id="${id}" hot-field="${this.field}" type="${this.type}" class="form-control${sizeClass}${classes}"${minAttr}${stepAttr}${maxAttr}${acAttr}${phAttr} />`;
101
+ }
102
+
103
+ private renderSelect (id: string, sizeClass: string, classes: string): string
104
+ {
105
+ const opts = (this.options || "").split (",").map ((raw) =>
106
+ {
107
+ const part = raw.trim ();
108
+ if (part === "") return "";
109
+ const colon = part.indexOf (":");
110
+ const val = colon >= 0 ? part.slice (0, colon) : part;
111
+ const lbl = colon >= 0 ? part.slice (colon + 1) : part;
112
+ return `<option value="${val.replace (/"/g, "&quot;")}">${lbl}</option>`;
113
+ }).join ("");
114
+ return `<select id="${id}" hot-field="${this.field}" class="form-select${sizeClass}${classes}">${opts}</select>`;
115
+ }
116
+
117
+ private renderTextarea (id: string, sizeClass: string, classes: string): string
118
+ {
119
+ const phAttr = this.placeholder ? ` placeholder="${this.placeholder.replace (/"/g, "&quot;")}"` : "";
120
+ const maxAttr = this.maxlength ? ` maxlength="${this.maxlength}"` : "";
121
+ return `<textarea id="${id}" hot-field="${this.field}" rows="${this.rows}" class="form-control${sizeClass}${classes}"${maxAttr}${phAttr}></textarea>`;
122
+ }
123
+
124
+ private renderCheckbox (id: string): string
125
+ {
126
+ return `<div class="form-check"><input id="${id}" hot-field="${this.field}" type="checkbox" class="form-check-input" /><label for="${id}" class="form-check-label">${this.label}</label></div>`;
127
+ }
128
+
129
+ output (): string | HotComponentOutput[]
130
+ {
131
+ if (this.field === "")
132
+ throw new Error ("admin-form-field: hot-field is required");
133
+
134
+ const id = `ff-${this.field}-${Math.random ().toString (36).slice (2, 7)}`;
135
+ const sizeClass = this.size === "sm" ? " form-control-sm" : "";
136
+ const classes = this.css_class ? " " + this.css_class : "";
137
+
138
+ // Checkbox is a special case — the label sits next to the box,
139
+ // not above. Skip the rest of the wrapping.
140
+ if (this.control === "checkbox")
141
+ {
142
+ const inner = this.renderCheckbox (id);
143
+ return (this.col ? `<div class="${this.col}">${inner}</div>` : inner);
144
+ }
145
+
146
+ let control = "";
147
+ if (this.control === "select") control = this.renderSelect (id, sizeClass, classes);
148
+ else if (this.control === "textarea") control = this.renderTextarea (id, sizeClass, classes);
149
+ else control = this.renderInput (id, sizeClass, classes);
150
+
151
+ const requiredMarker = (this.required === "1" || this.required === "true")
152
+ ? ` <span class="text-danger" aria-label="required">*</span>`
153
+ : "";
154
+
155
+ const helpHtml = this.help
156
+ ? `<div class="form-text small">${this.help}</div>`
157
+ : "";
158
+
159
+ const fieldHtml = `
160
+ <label for="${id}" class="form-label small mb-1">${this.label}${requiredMarker}</label>
161
+ ${control}
162
+ ${helpHtml}`;
163
+
164
+ return (this.col ? `<div class="${this.col}">${fieldHtml}</div>` : `<div>${fieldHtml}</div>`);
165
+ }
166
+ }
package/src/index.ts CHANGED
@@ -7,6 +7,16 @@ import { AdminTableField } from "./components/admin-table-field";
7
7
  import { AdminTableRow } from "./components/admin-table-row";
8
8
  import { AdminText } from "./components/admin-text";
9
9
 
10
+ // admin-panel 0.4.0 — Freelight-style components. Backwards compatible
11
+ // with the existing modal/table set above; pages opt in by switching
12
+ // their .hott templates to use the new tags.
13
+ import { AdminFormField } from "./components/admin-form-field";
14
+ import { AdminAddPanel } from "./components/admin-add-panel";
15
+ import { AdminCardTable } from "./components/admin-card-table";
16
+ import { AdminDetailPage } from "./components/admin-detail-page";
17
+ import { AdminDisclaimer } from "./components/admin-disclaimer";
18
+ import { AdminEyebrowHeading } from "./components/admin-eyebrow-heading";
19
+
10
20
  export {
11
21
  AdminButton,
12
22
  AdminDropdown,
@@ -15,5 +25,11 @@ export {
15
25
  AdminTable,
16
26
  AdminTableField,
17
27
  AdminTableRow,
18
- AdminText
19
- };
28
+ AdminText,
29
+ AdminFormField,
30
+ AdminAddPanel,
31
+ AdminCardTable,
32
+ AdminDetailPage,
33
+ AdminDisclaimer,
34
+ AdminEyebrowHeading
35
+ };