@mohasinac/appkit 2.7.35 → 2.7.37

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 (186) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +1 -1
  2. package/dist/_internal/client/features/seller/print-center/InventoryLabel.d.ts +12 -0
  3. package/dist/_internal/client/features/seller/print-center/InventoryLabel.js +49 -0
  4. package/dist/_internal/client/features/seller/print-center/LabelDesignPicker.d.ts +7 -0
  5. package/dist/_internal/client/features/seller/print-center/LabelDesignPicker.js +32 -0
  6. package/dist/_internal/client/features/seller/print-center/OrderPackingLabel.d.ts +28 -0
  7. package/dist/_internal/client/features/seller/print-center/OrderPackingLabel.js +33 -0
  8. package/dist/_internal/client/features/seller/print-center/PhysicalLocationModal.d.ts +12 -0
  9. package/dist/_internal/client/features/seller/print-center/PhysicalLocationModal.js +30 -0
  10. package/dist/_internal/client/features/seller/print-center/PrintCenterView.d.ts +12 -0
  11. package/dist/_internal/client/features/seller/print-center/PrintCenterView.js +102 -0
  12. package/dist/_internal/client/features/seller/print-center/PrintGrid.d.ts +12 -0
  13. package/dist/_internal/client/features/seller/print-center/PrintGrid.js +13 -0
  14. package/dist/_internal/client/features/seller/print-center/StoreCard.d.ts +19 -0
  15. package/dist/_internal/client/features/seller/print-center/StoreCard.js +30 -0
  16. package/dist/_internal/client/features/seller/print-center/WebsiteCard.d.ts +11 -0
  17. package/dist/_internal/client/features/seller/print-center/WebsiteCard.js +25 -0
  18. package/dist/_internal/client/features/seller/print-center/index.d.ts +11 -0
  19. package/dist/_internal/client/features/seller/print-center/index.js +10 -0
  20. package/dist/_internal/client/features/seller/print-center/types.d.ts +20 -0
  21. package/dist/_internal/client/features/seller/print-center/types.js +20 -0
  22. package/dist/_internal/client/features/seller/print-center/useInventoryPdf.d.ts +18 -0
  23. package/dist/_internal/client/features/seller/print-center/useInventoryPdf.js +76 -0
  24. package/dist/_internal/server/features/auctions/og.d.ts +1 -0
  25. package/dist/_internal/server/features/auctions/og.js +2 -1
  26. package/dist/_internal/server/features/blog/og.d.ts +1 -0
  27. package/dist/_internal/server/features/blog/og.js +3 -2
  28. package/dist/_internal/server/features/brands/og.d.ts +1 -0
  29. package/dist/_internal/server/features/brands/og.js +2 -1
  30. package/dist/_internal/server/features/bundles/og.d.ts +1 -0
  31. package/dist/_internal/server/features/bundles/og.js +2 -1
  32. package/dist/_internal/server/features/categories/og.d.ts +1 -0
  33. package/dist/_internal/server/features/categories/og.js +2 -1
  34. package/dist/_internal/server/features/checkout/actions.js +13 -5
  35. package/dist/_internal/server/features/classified/og.d.ts +2 -0
  36. package/dist/_internal/server/features/classified/og.js +2 -1
  37. package/dist/_internal/server/features/digital-code/og.d.ts +2 -0
  38. package/dist/_internal/server/features/digital-code/og.js +2 -1
  39. package/dist/_internal/server/features/events/og.d.ts +1 -0
  40. package/dist/_internal/server/features/events/og.js +3 -1
  41. package/dist/_internal/server/features/live/og.d.ts +2 -0
  42. package/dist/_internal/server/features/live/og.js +2 -1
  43. package/dist/_internal/server/features/pre-orders/og.d.ts +1 -0
  44. package/dist/_internal/server/features/pre-orders/og.js +2 -1
  45. package/dist/_internal/server/features/products/og.d.ts +3 -1
  46. package/dist/_internal/server/features/products/og.js +2 -1
  47. package/dist/_internal/server/features/seo/index.d.ts +1 -1
  48. package/dist/_internal/server/features/seo/index.js +1 -1
  49. package/dist/_internal/server/features/seo/og.d.ts +8 -1
  50. package/dist/_internal/server/features/seo/og.js +28 -8
  51. package/dist/_internal/server/features/stores/og.d.ts +1 -0
  52. package/dist/_internal/server/features/stores/og.js +3 -2
  53. package/dist/_internal/server/features/sublisting-categories/og.d.ts +1 -0
  54. package/dist/_internal/server/features/sublisting-categories/og.js +2 -1
  55. package/dist/_internal/server/jobs/core/draftPrune.d.ts +2 -0
  56. package/dist/_internal/server/jobs/core/draftPrune.js +15 -0
  57. package/dist/_internal/server/jobs/core/onBidPlaced.js +1 -1
  58. package/dist/_internal/server/jobs/core/onScamReportCreate.js +18 -14
  59. package/dist/_internal/server/jobs/core/onScamReportRejected.js +10 -7
  60. package/dist/_internal/server/jobs/core/onScamReportVerified.js +10 -7
  61. package/dist/_internal/server/jobs/core/promotions.d.ts +1 -0
  62. package/dist/_internal/server/jobs/core/promotions.js +21 -6
  63. package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +18 -1
  64. package/dist/_internal/server/jobs/handlers/draftPrune.d.ts +2 -0
  65. package/dist/_internal/server/jobs/handlers/draftPrune.js +2 -0
  66. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  67. package/dist/_internal/server/jobs/handlers/index.js +2 -0
  68. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +15 -1
  69. package/dist/_internal/shared/actions/action-registry.js +32 -0
  70. package/dist/_internal/shared/features/blog/schema.d.ts +8 -8
  71. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +10 -10
  72. package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
  73. package/dist/_internal/shared/features/products/schema.d.ts +8 -8
  74. package/dist/_internal/shared/features/promotions/schema.d.ts +6 -6
  75. package/dist/_internal/shared/features/reviews/schema.d.ts +2 -2
  76. package/dist/client.d.ts +13 -1
  77. package/dist/client.js +7 -1
  78. package/dist/configs/next.js +1 -0
  79. package/dist/constants/api-endpoints.d.ts +15 -0
  80. package/dist/constants/api-endpoints.js +5 -0
  81. package/dist/core/server-action.d.ts +4 -0
  82. package/dist/errors/error-codes.d.ts +18 -0
  83. package/dist/errors/error-codes.js +18 -0
  84. package/dist/errors/index.d.ts +1 -1
  85. package/dist/errors/index.js +1 -1
  86. package/dist/errors/messages.d.ts +1 -0
  87. package/dist/errors/messages.js +1 -0
  88. package/dist/features/account/components/UserSidebar.js +1 -1
  89. package/dist/features/account/schemas/index.d.ts +12 -12
  90. package/dist/features/admin/components/AdminBundlesView.js +20 -2
  91. package/dist/features/admin/components/AdminPayoutsView.js +2 -1
  92. package/dist/features/admin/components/AdminProductsView.js +13 -0
  93. package/dist/features/admin/components/AdminSidebar.js +1 -1
  94. package/dist/features/admin/components/AdminStoresView.js +43 -5
  95. package/dist/features/admin/components/AdminUsersView.js +53 -6
  96. package/dist/features/admin/schemas/firestore.d.ts +1 -1
  97. package/dist/features/auctions/actions/bid-actions.js +13 -6
  98. package/dist/features/auctions/components/PlaceBidFormClient.js +10 -2
  99. package/dist/features/auctions/repository/bid.repository.d.ts +7 -0
  100. package/dist/features/auctions/repository/bid.repository.js +16 -0
  101. package/dist/features/auctions/schemas/index.d.ts +18 -18
  102. package/dist/features/auth/schemas/index.d.ts +2 -2
  103. package/dist/features/before-after/schemas/index.d.ts +2 -2
  104. package/dist/features/blog/actions/blog-actions.d.ts +32 -32
  105. package/dist/features/blog/components/BlogPostForm.js +2 -1
  106. package/dist/features/blog/schemas/index.d.ts +38 -38
  107. package/dist/features/categories/components/CategoryForm.js +1 -0
  108. package/dist/features/categories/schemas/index.d.ts +10 -10
  109. package/dist/features/collections/schemas/index.d.ts +4 -4
  110. package/dist/features/consultation/schemas/index.d.ts +9 -9
  111. package/dist/features/corporate/schemas/index.d.ts +4 -4
  112. package/dist/features/events/schemas/index.d.ts +50 -50
  113. package/dist/features/faq/actions/faq-actions.d.ts +6 -6
  114. package/dist/features/faq/schemas/index.d.ts +8 -8
  115. package/dist/features/homepage/actions/homepage-section-actions.d.ts +4 -4
  116. package/dist/features/homepage/components/SectionCarousel.js +1 -8
  117. package/dist/features/layout/AppLayoutShell.js +1 -1
  118. package/dist/features/layout/AutoBreadcrumbs.js +1 -1
  119. package/dist/features/media/AvatarUpload.js +1 -1
  120. package/dist/features/media/types/index.d.ts +4 -4
  121. package/dist/features/media/upload/ImageUpload.js +18 -9
  122. package/dist/features/media/upload/MediaUploadList.js +2 -2
  123. package/dist/features/orders/schemas/firestore.d.ts +6 -0
  124. package/dist/features/orders/schemas/index.d.ts +10 -10
  125. package/dist/features/orders/types/index.d.ts +5 -0
  126. package/dist/features/payments/schemas/index.d.ts +4 -4
  127. package/dist/features/pre-orders/schemas/index.d.ts +8 -8
  128. package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
  129. package/dist/features/products/components/ProductForm.js +20 -2
  130. package/dist/features/products/repository/products.repository.d.ts +3 -1
  131. package/dist/features/products/repository/products.repository.js +12 -1
  132. package/dist/features/products/schemas/firestore.d.ts +7 -0
  133. package/dist/features/products/schemas/index.d.ts +34 -34
  134. package/dist/features/products/schemas/product-features.validators.d.ts +6 -6
  135. package/dist/features/products/types/index.d.ts +6 -0
  136. package/dist/features/promotions/schemas/index.d.ts +14 -14
  137. package/dist/features/reviews/schemas/index.d.ts +18 -18
  138. package/dist/features/seller/actions/offer-actions.js +10 -1
  139. package/dist/features/seller/components/PhysicalLocationModal.d.ts +12 -0
  140. package/dist/features/seller/components/PhysicalLocationModal.js +30 -0
  141. package/dist/features/seller/components/PrintCenterView.d.ts +40 -0
  142. package/dist/features/seller/components/PrintCenterView.js +6 -0
  143. package/dist/features/seller/components/SellerOrdersView.js +48 -3
  144. package/dist/features/seller/components/SellerProductShell.d.ts +1 -0
  145. package/dist/features/seller/components/SellerProductShell.js +26 -4
  146. package/dist/features/seller/components/SellerProductsView.js +54 -13
  147. package/dist/features/seller/components/SellerSidebar.js +1 -1
  148. package/dist/features/seller/components/index.d.ts +1 -0
  149. package/dist/features/seller/components/index.js +1 -0
  150. package/dist/features/seller/schemas/firestore.d.ts +2 -0
  151. package/dist/features/seller/schemas/firestore.js +1 -0
  152. package/dist/features/seller/schemas/index.d.ts +22 -22
  153. package/dist/features/shell/FormShell.js +1 -1
  154. package/dist/features/shell/StepForm.d.ts +3 -1
  155. package/dist/features/shell/StepForm.js +3 -3
  156. package/dist/features/stores/schemas/index.d.ts +4 -4
  157. package/dist/index.d.ts +9 -1
  158. package/dist/index.js +6 -1
  159. package/dist/next/routing/route-map.d.ts +8 -0
  160. package/dist/next/routing/route-map.js +3 -0
  161. package/dist/seed/homepage-sections-seed-data.js +10 -10
  162. package/dist/server.d.ts +1 -1
  163. package/dist/server.js +1 -1
  164. package/dist/tailwind-utilities.css +1 -1
  165. package/dist/ui/components/FormField.js +1 -0
  166. package/dist/ui/components/RowActionMenu.js +45 -10
  167. package/dist/ui/components/SideDrawer.style.css +2 -2
  168. package/dist/ui/forms/FieldCheckbox.d.ts +13 -0
  169. package/dist/ui/forms/FieldCheckbox.js +25 -0
  170. package/dist/ui/forms/FieldInput.d.ts +11 -0
  171. package/dist/ui/forms/FieldInput.js +25 -0
  172. package/dist/ui/forms/FieldSelect.d.ts +17 -0
  173. package/dist/ui/forms/FieldSelect.js +25 -0
  174. package/dist/ui/forms/FormShell.d.ts +48 -0
  175. package/dist/ui/forms/FormShell.js +176 -0
  176. package/dist/ui/forms/FormShell.style.css +287 -0
  177. package/dist/ui/forms/index.d.ts +8 -0
  178. package/dist/ui/forms/index.js +4 -0
  179. package/dist/ui/index.d.ts +9 -0
  180. package/dist/ui/index.js +5 -0
  181. package/dist/utils/action-response.d.ts +6 -0
  182. package/dist/utils/action-response.js +22 -0
  183. package/dist/utils/index.d.ts +1 -0
  184. package/dist/utils/index.js +1 -0
  185. package/dist/validation/schemas.d.ts +10 -10
  186. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  import { Input } from "./Input";
3
4
  import { Select } from "./Select";
@@ -1,19 +1,54 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useRef, useState } from "react";
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { createPortal } from "react-dom";
4
5
  import { MoreHorizontal } from "lucide-react";
5
6
  import { Button } from "./Button";
6
7
  import { Span } from "./Typography";
7
- import { useClickOutside, useKeyPress } from "../../react";
8
+ import { useKeyPress } from "../../react";
8
9
  export function RowActionMenu({ actions, align = "right", }) {
9
10
  const [open, setOpen] = useState(false);
10
- const ref = useRef(null);
11
- useClickOutside(ref, () => setOpen(false), { enabled: open });
11
+ const [pos, setPos] = useState(null);
12
+ const wrapperRef = useRef(null);
13
+ const dropdownRef = useRef(null);
14
+ const [mounted, setMounted] = useState(false);
15
+ useEffect(() => setMounted(true), []);
12
16
  useKeyPress("Escape", () => setOpen(false), { enabled: open });
13
- return (_jsxs("div", { ref: ref, className: "appkit-row-action-menu", onClick: (e) => e.stopPropagation(), children: [_jsx(Button, { type: "button", variant: "ghost", "aria-label": "Row actions", "aria-haspopup": "menu", "aria-expanded": open, onClick: () => setOpen((v) => !v), className: "appkit-row-action-menu__trigger", children: _jsx(MoreHorizontal, { className: "h-4 w-4", strokeWidth: 1.5 }) }), open ? (_jsx("div", { role: "menu", className: `appkit-row-action-menu__dropdown ${align === "right" ? "appkit-row-action-menu__dropdown--right" : "appkit-row-action-menu__dropdown--left"}`, "data-section": "rowactionmenu-div-585", children: actions.map((action, idx) => (_jsxs("div", { "data-section": "rowactionmenu-div-586", children: [action.separator && idx > 0 ? (_jsx("div", { className: "appkit-row-action-menu__separator" })) : null, _jsxs(Button, { type: "button", role: "menuitem", variant: "ghost", disabled: action.disabled, onClick: () => {
14
- if (action.disabled)
15
- return;
16
- action.onClick();
17
- setOpen(false);
18
- }, className: `appkit-row-action-menu__item ${action.destructive ? "appkit-row-action-menu__item--destructive" : ""} ${action.disabled ? "opacity-40 cursor-not-allowed" : ""}`, children: [action.icon ? (_jsx(Span, { className: "appkit-row-action-menu__icon", children: action.icon })) : null, action.label] })] }, `${action.label}-${idx}`))) })) : null] }));
17
+ // Close on outside click exclude both trigger wrapper and portal dropdown
18
+ useEffect(() => {
19
+ if (!open)
20
+ return;
21
+ const handler = (e) => {
22
+ const t = e.target;
23
+ if (!wrapperRef.current?.contains(t) && !dropdownRef.current?.contains(t)) {
24
+ setOpen(false);
25
+ }
26
+ };
27
+ document.addEventListener("mousedown", handler);
28
+ return () => document.removeEventListener("mousedown", handler);
29
+ }, [open]);
30
+ const handleToggle = useCallback(() => {
31
+ if (!open && wrapperRef.current) {
32
+ const rect = wrapperRef.current.getBoundingClientRect();
33
+ const top = rect.bottom + 4;
34
+ setPos(align === "right"
35
+ ? { top, right: window.innerWidth - rect.right }
36
+ : { top, left: rect.left });
37
+ }
38
+ setOpen((v) => !v);
39
+ }, [open, align]);
40
+ const dropdown = open && pos && mounted
41
+ ? createPortal(_jsx("div", { ref: dropdownRef, role: "menu", className: "appkit-row-action-menu__dropdown", style: {
42
+ position: "fixed",
43
+ zIndex: "var(--appkit-z-modal)",
44
+ top: pos.top,
45
+ ...(pos.right !== undefined ? { right: pos.right } : { left: pos.left }),
46
+ }, onClick: (e) => e.stopPropagation(), children: actions.map((action, idx) => (_jsxs("div", { children: [action.separator && idx > 0 ? (_jsx("div", { className: "appkit-row-action-menu__separator" })) : null, _jsxs(Button, { type: "button", role: "menuitem", variant: "ghost", disabled: action.disabled, onClick: () => {
47
+ if (action.disabled)
48
+ return;
49
+ action.onClick();
50
+ setOpen(false);
51
+ }, className: `appkit-row-action-menu__item ${action.destructive ? "appkit-row-action-menu__item--destructive" : ""} ${action.disabled ? "opacity-40 cursor-not-allowed" : ""}`, children: [action.icon ? (_jsx(Span, { className: "appkit-row-action-menu__icon", children: action.icon })) : null, action.label] })] }, `${action.label}-${idx}`))) }), document.body)
52
+ : null;
53
+ return (_jsxs("div", { ref: wrapperRef, className: "appkit-row-action-menu", onClick: (e) => e.stopPropagation(), children: [_jsx(Button, { type: "button", variant: "ghost", "aria-label": "Row actions", "aria-haspopup": "menu", "aria-expanded": open, onClick: handleToggle, className: "appkit-row-action-menu__trigger", children: _jsx(MoreHorizontal, { className: "h-4 w-4", strokeWidth: 1.5 }) }), dropdown] }));
19
54
  }
@@ -158,7 +158,7 @@
158
158
  .appkit-side-drawer__content {
159
159
  flex: 1;
160
160
  overflow-y: auto;
161
- padding: 1rem;
161
+ padding: 1rem 1.25rem;
162
162
  }
163
163
  @media (min-width: 640px) {
164
164
  .appkit-side-drawer__content { padding: 1.5rem; }
@@ -167,7 +167,7 @@
167
167
  /* Footer */
168
168
  .appkit-side-drawer__footer {
169
169
  flex-shrink: 0;
170
- padding: 1rem 1rem;
170
+ padding: 1rem 1.25rem;
171
171
  border-top: 1px solid var(--appkit-color-border);
172
172
  background-color: var(--appkit-color-bg);
173
173
  }
@@ -0,0 +1,13 @@
1
+ export interface FieldCheckboxProps {
2
+ name: string;
3
+ label: string;
4
+ hint?: string;
5
+ checked?: boolean;
6
+ onChange?: (checked: boolean) => void;
7
+ onBlur?: () => void;
8
+ disabled?: boolean;
9
+ /** Override error — falls back to FormShell context error for this field */
10
+ error?: string;
11
+ className?: string;
12
+ }
13
+ export declare function FieldCheckbox({ name, label, hint, checked, onChange, onBlur, disabled, error: errorProp, className, }: FieldCheckboxProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useContext } from "react";
4
+ import { FormShellContext } from "./FormShell";
5
+ import { Checkbox } from "../components/Checkbox";
6
+ import { Text } from "../components/Typography";
7
+ export function FieldCheckbox({ name, label, hint, checked, onChange, onBlur, disabled, error: errorProp, className, }) {
8
+ const ctx = useContext(FormShellContext);
9
+ const contextError = ctx?.errors[name];
10
+ const error = errorProp ?? contextError;
11
+ const touched = ctx?.touched[name];
12
+ const showError = !!error && (touched != null ? touched : true);
13
+ const inputId = `field-${name}`;
14
+ const errorId = `${inputId}-error`;
15
+ function handleChange(e) {
16
+ onChange?.(e.target.checked);
17
+ if (showError)
18
+ ctx?.clearFieldError(name);
19
+ }
20
+ function handleBlur() {
21
+ ctx?.setFieldTouched(name);
22
+ onBlur?.();
23
+ }
24
+ return (_jsxs("div", { className: ["appkit-form-field", className].filter(Boolean).join(" "), children: [_jsx(Checkbox, { id: inputId, name: name, checked: checked, onChange: handleChange, onBlur: handleBlur, label: label, disabled: disabled, error: showError ? error : undefined, "aria-describedby": showError ? errorId : undefined }), !showError && hint && (_jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: hint })), showError && (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error }))] }));
25
+ }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ export interface FieldInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange"> {
3
+ name: string;
4
+ label?: string;
5
+ hint?: string;
6
+ required?: boolean;
7
+ onChange?: (value: string) => void;
8
+ /** Override error — falls back to FormShell context error for this field */
9
+ error?: string;
10
+ }
11
+ export declare function FieldInput({ name, label, hint, required, onChange, error: errorProp, onBlur, className, ...inputProps }: FieldInputProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useContext } from "react";
4
+ import { FormShellContext } from "./FormShell";
5
+ import { Input } from "../components/Input";
6
+ import { Label, Text, Span } from "../components/Typography";
7
+ export function FieldInput({ name, label, hint, required, onChange, error: errorProp, onBlur, className, ...inputProps }) {
8
+ const ctx = useContext(FormShellContext);
9
+ const contextError = ctx?.errors[name];
10
+ const error = errorProp ?? contextError;
11
+ const touched = ctx?.touched[name];
12
+ const showError = !!error && (touched != null ? touched : true);
13
+ const inputId = `field-${name}`;
14
+ const errorId = `${inputId}-error`;
15
+ function handleBlur(e) {
16
+ ctx?.setFieldTouched(name);
17
+ onBlur?.(e);
18
+ }
19
+ function handleChange(e) {
20
+ onChange?.(e.target.value);
21
+ if (showError)
22
+ ctx?.clearFieldError(name);
23
+ }
24
+ return (_jsxs("div", { className: ["appkit-form-field", className].filter(Boolean).join(" "), children: [label && (_jsxs(Label, { htmlFor: inputId, className: "appkit-form-field__label", children: [label, required && _jsx(Span, { className: "appkit-form-field__required", children: " *" })] })), _jsx(Input, { id: inputId, name: name, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": showError ? errorId : undefined, onBlur: handleBlur, onChange: handleChange, ...inputProps }), !showError && hint && (_jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: hint })), showError && (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error }))] }));
25
+ }
@@ -0,0 +1,17 @@
1
+ import { type SelectOption } from "../components/Select";
2
+ export interface FieldSelectProps {
3
+ name: string;
4
+ label?: string;
5
+ hint?: string;
6
+ required?: boolean;
7
+ value?: string;
8
+ onChange?: (value: string) => void;
9
+ onBlur?: () => void;
10
+ options: SelectOption[];
11
+ disabled?: boolean;
12
+ placeholder?: string;
13
+ /** Override error — falls back to FormShell context error for this field */
14
+ error?: string;
15
+ className?: string;
16
+ }
17
+ export declare function FieldSelect({ name, label, hint, required, value, onChange, onBlur, options, disabled, placeholder, error: errorProp, className, }: FieldSelectProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useContext } from "react";
4
+ import { FormShellContext } from "./FormShell";
5
+ import { Select } from "../components/Select";
6
+ import { Label, Text, Span } from "../components/Typography";
7
+ export function FieldSelect({ name, label, hint, required, value, onChange, onBlur, options, disabled, placeholder, error: errorProp, className, }) {
8
+ const ctx = useContext(FormShellContext);
9
+ const contextError = ctx?.errors[name];
10
+ const error = errorProp ?? contextError;
11
+ const touched = ctx?.touched[name];
12
+ const showError = !!error && (touched != null ? touched : true);
13
+ const inputId = `field-${name}`;
14
+ const errorId = `${inputId}-error`;
15
+ function handleBlur() {
16
+ ctx?.setFieldTouched(name);
17
+ onBlur?.();
18
+ }
19
+ function handleChange(e) {
20
+ onChange?.(e.target.value);
21
+ if (showError)
22
+ ctx?.clearFieldError(name);
23
+ }
24
+ return (_jsxs("div", { className: ["appkit-form-field", className].filter(Boolean).join(" "), children: [label && (_jsxs(Label, { htmlFor: inputId, className: "appkit-form-field__label", children: [label, required && _jsx(Span, { className: "appkit-form-field__required", children: " *" })] })), _jsx(Select, { id: inputId, name: name, value: value, onChange: handleChange, onBlur: handleBlur, options: options, disabled: disabled, placeholder: placeholder, "aria-required": required || undefined, "aria-invalid": showError || undefined, "aria-describedby": showError ? errorId : undefined }), !showError && hint && (_jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: hint })), showError && (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error }))] }));
25
+ }
@@ -0,0 +1,48 @@
1
+ import React from "react";
2
+ export interface FormShellStep {
3
+ id: string;
4
+ label: string;
5
+ /** Field names required in this step — drives publish gate + error badge */
6
+ requiredFields?: string[];
7
+ content: React.ReactNode;
8
+ }
9
+ export interface FormShellContextValue {
10
+ errors: Record<string, string>;
11
+ touched: Record<string, boolean>;
12
+ setFieldError: (name: string, error: string | null) => void;
13
+ setFieldTouched: (name: string) => void;
14
+ clearFieldError: (name: string) => void;
15
+ steps: FormShellStep[];
16
+ currentStep: number;
17
+ goToStep: (n: number) => void;
18
+ nextStep: () => void;
19
+ prevStep: () => void;
20
+ isPublishReady: boolean;
21
+ isDirty: boolean;
22
+ isSubmitting: boolean;
23
+ stepErrorCounts: number[];
24
+ }
25
+ export interface FormShellProps {
26
+ steps: FormShellStep[];
27
+ /** Called on debounced auto-save and "Save Draft" click */
28
+ onSaveDraft?: (values: Record<string, unknown>) => Promise<{
29
+ id?: string;
30
+ } | void>;
31
+ /** Called on Publish / Save */
32
+ onPublish: (values: Record<string, unknown>) => Promise<void>;
33
+ /** Current form values — caller owns state */
34
+ values: Record<string, unknown>;
35
+ /** Whether any values differ from the persisted state */
36
+ isDirty?: boolean;
37
+ draftId?: string;
38
+ autoSaveDelayMs?: number;
39
+ publishLabel?: string;
40
+ saveDraftLabel?: string;
41
+ title?: string;
42
+ className?: string;
43
+ /** If true, show steps as vertical accordion on mobile instead of wizard tabs */
44
+ mobileAccordion?: boolean;
45
+ }
46
+ export declare const FormShellContext: React.Context<FormShellContextValue | null>;
47
+ export declare function useFormShell(): FormShellContextValue;
48
+ export declare function FormShell({ steps, onSaveDraft, onPublish, values, isDirty, autoSaveDelayMs, publishLabel, saveDraftLabel, title, className, mobileAccordion, }: FormShellProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,176 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
4
+ import { Button } from "../components/Button";
5
+ import { Heading, Text } from "../components/Typography";
6
+ // ─── Context ──────────────────────────────────────────────────────────────────
7
+ export const FormShellContext = createContext(null);
8
+ export function useFormShell() {
9
+ const ctx = useContext(FormShellContext);
10
+ if (!ctx)
11
+ throw new Error("useFormShell must be used inside <FormShell>");
12
+ return ctx;
13
+ }
14
+ function StepIndicator({ steps, currentStep, stepErrorCounts, onGoTo, }) {
15
+ return (_jsx("nav", { className: "appkit-formshell__step-indicator", "aria-label": "Form steps", children: steps.map((step, i) => {
16
+ const isActive = i === currentStep;
17
+ const hasErrors = stepErrorCounts[i] > 0;
18
+ return (_jsxs("button", { type: "button", className: [
19
+ "appkit-formshell__step-btn",
20
+ isActive ? "appkit-formshell__step-btn--active" : "",
21
+ hasErrors ? "appkit-formshell__step-btn--error" : "",
22
+ ]
23
+ .filter(Boolean)
24
+ .join(" "), onClick: () => onGoTo(i), "aria-current": isActive ? "step" : undefined, "aria-label": `${step.label}${hasErrors ? ` (${stepErrorCounts[i]} error${stepErrorCounts[i] !== 1 ? "s" : ""})` : ""}`, children: [_jsxs("span", { className: "appkit-formshell__step-dot", children: [hasErrors && (_jsx("span", { className: "appkit-formshell__step-error-badge", "aria-hidden": "true" })), _jsx("span", { className: "appkit-formshell__step-num", children: i + 1 })] }), _jsx("span", { className: "appkit-formshell__step-label", children: step.label })] }, step.id));
25
+ }) }));
26
+ }
27
+ function ErrorSummary({ errors, visible }) {
28
+ const entries = Object.entries(errors).filter(([, v]) => v);
29
+ if (!visible || entries.length === 0)
30
+ return null;
31
+ return (_jsxs("div", { className: "appkit-formshell__error-summary", role: "alert", "aria-live": "assertive", children: [_jsxs(Text, { size: "sm", variant: "error", className: "appkit-formshell__error-summary__title", children: ["Please fix ", entries.length, " error", entries.length !== 1 ? "s" : "", " before publishing."] }), _jsx("ul", { className: "appkit-formshell__error-summary__list", children: entries.map(([field, msg]) => (_jsx("li", { children: _jsxs(Text, { size: "sm", variant: "error", children: [_jsx("strong", { children: field }), ": ", msg] }) }, field))) })] }));
32
+ }
33
+ // ─── FormShell ────────────────────────────────────────────────────────────────
34
+ export function FormShell({ steps, onSaveDraft, onPublish, values, isDirty = false, autoSaveDelayMs = 2000, publishLabel = "Publish", saveDraftLabel = "Save Draft", title, className = "", mobileAccordion = true, }) {
35
+ const [errors, setErrors] = useState({});
36
+ const [touched, setTouched] = useState({});
37
+ const [currentStep, setCurrentStep] = useState(0);
38
+ const [isSubmitting, setIsSubmitting] = useState(false);
39
+ const [showErrorSummary, setShowErrorSummary] = useState(false);
40
+ // Auto-save
41
+ const autoSaveTimer = useRef(null);
42
+ const valuesRef = useRef(values);
43
+ valuesRef.current = values;
44
+ useEffect(() => {
45
+ if (!onSaveDraft || !isDirty)
46
+ return;
47
+ if (autoSaveTimer.current)
48
+ clearTimeout(autoSaveTimer.current);
49
+ autoSaveTimer.current = setTimeout(() => {
50
+ onSaveDraft(valuesRef.current).catch(() => { });
51
+ }, autoSaveDelayMs);
52
+ return () => {
53
+ if (autoSaveTimer.current)
54
+ clearTimeout(autoSaveTimer.current);
55
+ };
56
+ }, [values, isDirty, onSaveDraft, autoSaveDelayMs]);
57
+ // Per-step error counts
58
+ const stepErrorCounts = useMemo(() => {
59
+ return steps.map((step) => {
60
+ const fields = step.requiredFields ?? [];
61
+ return fields.filter((f) => errors[f]).length;
62
+ });
63
+ }, [steps, errors]);
64
+ // Publish gate: all required fields across all steps must be valid
65
+ const isPublishReady = useMemo(() => {
66
+ return steps.every((step) => (step.requiredFields ?? []).every((f) => !errors[f]));
67
+ }, [steps, errors]);
68
+ const setFieldError = useCallback((name, error) => {
69
+ setErrors((prev) => {
70
+ if (!error) {
71
+ const next = { ...prev };
72
+ delete next[name];
73
+ return next;
74
+ }
75
+ if (prev[name] === error)
76
+ return prev;
77
+ return { ...prev, [name]: error };
78
+ });
79
+ }, []);
80
+ const clearFieldError = useCallback((name) => {
81
+ setErrors((prev) => {
82
+ if (!prev[name])
83
+ return prev;
84
+ const next = { ...prev };
85
+ delete next[name];
86
+ return next;
87
+ });
88
+ }, []);
89
+ const setFieldTouched = useCallback((name) => {
90
+ setTouched((prev) => (prev[name] ? prev : { ...prev, [name]: true }));
91
+ }, []);
92
+ const goToStep = useCallback((n) => {
93
+ if (n >= 0 && n < steps.length)
94
+ setCurrentStep(n);
95
+ }, [steps.length]);
96
+ const nextStep = useCallback(() => setCurrentStep((s) => Math.min(s + 1, steps.length - 1)), [steps.length]);
97
+ const prevStep = useCallback(() => setCurrentStep((s) => Math.max(s - 1, 0)), []);
98
+ const handleSaveDraft = useCallback(async () => {
99
+ if (!onSaveDraft)
100
+ return;
101
+ setIsSubmitting(true);
102
+ try {
103
+ await onSaveDraft(values);
104
+ }
105
+ finally {
106
+ setIsSubmitting(false);
107
+ }
108
+ }, [onSaveDraft, values]);
109
+ const handlePublish = useCallback(async () => {
110
+ setShowErrorSummary(true);
111
+ if (!isPublishReady)
112
+ return;
113
+ setIsSubmitting(true);
114
+ try {
115
+ await onPublish(values);
116
+ }
117
+ finally {
118
+ setIsSubmitting(false);
119
+ }
120
+ }, [isPublishReady, onPublish, values]);
121
+ const ctx = useMemo(() => ({
122
+ errors,
123
+ touched,
124
+ setFieldError,
125
+ setFieldTouched,
126
+ clearFieldError,
127
+ steps,
128
+ currentStep,
129
+ goToStep,
130
+ nextStep,
131
+ prevStep,
132
+ isPublishReady,
133
+ isDirty,
134
+ isSubmitting,
135
+ stepErrorCounts,
136
+ }), [
137
+ errors,
138
+ touched,
139
+ setFieldError,
140
+ setFieldTouched,
141
+ clearFieldError,
142
+ steps,
143
+ currentStep,
144
+ goToStep,
145
+ nextStep,
146
+ prevStep,
147
+ isPublishReady,
148
+ isDirty,
149
+ isSubmitting,
150
+ stepErrorCounts,
151
+ ]);
152
+ const isMultiStep = steps.length > 1;
153
+ return (_jsx(FormShellContext.Provider, { value: ctx, children: _jsxs("div", { className: [
154
+ "appkit-formshell",
155
+ mobileAccordion ? "appkit-formshell--accordion-mobile" : "",
156
+ className,
157
+ ]
158
+ .filter(Boolean)
159
+ .join(" "), children: [(title || isMultiStep) && (_jsxs("div", { className: "appkit-formshell__header", children: [title && (_jsx(Heading, { level: 2, className: "appkit-formshell__title", children: title })), isMultiStep && (_jsx(StepIndicator, { steps: steps, currentStep: currentStep, stepErrorCounts: stepErrorCounts, onGoTo: goToStep }))] })), _jsx(ErrorSummary, { errors: errors, visible: showErrorSummary }), _jsx("div", { className: "appkit-formshell__body", children: isMultiStep ? (mobileAccordion ? (
160
+ // Mobile: vertical accordion; Desktop: show only current step
161
+ _jsx("div", { className: "appkit-formshell__steps", children: steps.map((step, i) => {
162
+ const isActive = i === currentStep;
163
+ const hasErrors = stepErrorCounts[i] > 0;
164
+ return (_jsxs("div", { className: [
165
+ "appkit-formshell__step",
166
+ isActive ? "appkit-formshell__step--active" : "",
167
+ hasErrors ? "appkit-formshell__step--error" : "",
168
+ ]
169
+ .filter(Boolean)
170
+ .join(" "), children: [_jsxs("button", { type: "button", className: "appkit-formshell__step-accordion-btn", onClick: () => goToStep(i), "aria-expanded": isActive, children: [_jsxs("span", { className: "appkit-formshell__step-accordion-label", children: [i + 1, ". ", step.label] }), hasErrors && (_jsx("span", { className: "appkit-formshell__step-error-badge appkit-formshell__step-error-badge--inline", children: stepErrorCounts[i] }))] }), _jsx("div", { className: "appkit-formshell__step-content", hidden: !isActive, "aria-hidden": !isActive, children: step.content })] }, step.id));
171
+ }) })) : (
172
+ // Non-accordion: show only the active step
173
+ _jsx("div", { className: "appkit-formshell__step-content", children: steps[currentStep]?.content }))) : (
174
+ // Single step: render content directly
175
+ _jsx("div", { className: "appkit-formshell__step-content", children: steps[0]?.content })) }), _jsxs("div", { className: "appkit-formshell__footer", children: [isMultiStep && (_jsxs("div", { className: "appkit-formshell__step-nav", children: [_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: prevStep, disabled: currentStep === 0 || isSubmitting, children: "Previous" }), currentStep < steps.length - 1 && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: nextStep, disabled: isSubmitting, children: "Next" }))] })), _jsxs("div", { className: "appkit-formshell__actions", children: [onSaveDraft && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleSaveDraft, isLoading: isSubmitting, disabled: isSubmitting, children: saveDraftLabel })), _jsx(Button, { type: "button", size: "sm", onClick: handlePublish, isLoading: isSubmitting, disabled: isSubmitting || (showErrorSummary && !isPublishReady), children: publishLabel })] })] })] }) }));
176
+ }