najm-kit 0.0.28 → 0.0.30

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/dist/index.d.ts CHANGED
@@ -2139,6 +2139,7 @@ interface WizardClassNames {
2139
2139
  indicator?: string;
2140
2140
  progressBar?: string;
2141
2141
  }
2142
+ type WizardFooterDivider = boolean | "none" | "solid" | "dashed" | "dotted";
2142
2143
  interface WizardFormProps {
2143
2144
  steps: StepConfig[];
2144
2145
  schema: any;
@@ -2157,6 +2158,8 @@ interface WizardFormProps {
2157
2158
  className?: string;
2158
2159
  classNames?: WizardClassNames;
2159
2160
  footerSlot?: ReactNode;
2161
+ footerDivider?: WizardFooterDivider;
2162
+ footerDividerClassName?: string;
2160
2163
  children?: ReactNode;
2161
2164
  }
2162
2165
  interface StepMeta {
@@ -2165,7 +2168,7 @@ interface StepMeta {
2165
2168
  title: string;
2166
2169
  }
2167
2170
 
2168
- declare function WizardForm({ steps, schema, defaultValues, onSubmit, currentStep: controlledStep, onCurrentStepChange, onStepComplete, showHeader, showFooter, nextLabel, previousLabel, submitLabel, variant, bordered, className, classNames, footerSlot, }: WizardFormProps): react_jsx_runtime.JSX.Element;
2171
+ declare function WizardForm({ steps, schema, defaultValues, onSubmit, currentStep: controlledStep, onCurrentStepChange, onStepComplete, showHeader, showFooter, nextLabel, previousLabel, submitLabel, variant, bordered, className, classNames, footerSlot, footerDivider, footerDividerClassName, }: WizardFormProps): react_jsx_runtime.JSX.Element;
2169
2172
 
2170
2173
  interface StepIndicatorProps {
2171
2174
  stepNumber: number;
@@ -2228,9 +2231,16 @@ interface UseFormSubmissionOptions {
2228
2231
  }
2229
2232
  interface FormSubmissionState {
2230
2233
  formDataRef: React.MutableRefObject<Record<string, any>>;
2231
- handleStepSubmit: (stepData: any) => Promise<void>;
2234
+ handleStepSubmit: (stepData: any) => Promise<StepSubmitResult>;
2232
2235
  getStepDefaultValues: (stepId: string) => Record<string, any>;
2233
2236
  }
2237
+ type StepSubmitResult = {
2238
+ ok: true;
2239
+ } | {
2240
+ ok: false;
2241
+ error: any;
2242
+ data: Record<string, any>;
2243
+ };
2234
2244
  declare function useFormSubmission({ steps, schema, defaultValues, onSubmit, currentStep, isLastStep, handleNext, markStepCompleted, reset, }: UseFormSubmissionOptions): FormSubmissionState;
2235
2245
 
2236
2246
  interface NTableClassNames {
package/dist/index.mjs CHANGED
@@ -5787,16 +5787,35 @@ var PasswordInput = ({ value, onChange, placeholder = "", icon, showIcon = true,
5787
5787
  showPassword ? /* @__PURE__ */ jsx(Eye, { className: "absolute right-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground cursor-pointer", onClick: () => setShowPassword(false) }) : /* @__PURE__ */ jsx(EyeOff, { className: "absolute right-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground cursor-pointer", onClick: () => setShowPassword(true) })
5788
5788
  ] });
5789
5789
  };
5790
- var TextAreaInput = ({ value, onChange, placeholder = "", className = "", variant = "default", status = "default", bordered, borderColor, rows }) => /* @__PURE__ */ jsx(BaseInput, { variant, status, bordered, borderColor, className: cn("items-start", className), children: /* @__PURE__ */ jsx(
5791
- Textarea,
5792
- {
5793
- rows,
5794
- placeholder,
5795
- value,
5796
- onChange: (ev) => onChange(ev.target.value),
5797
- className: "h-full border-0 bg-transparent p-0 text-muted-foreground shadow-none focus-visible:border-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 dark:bg-transparent"
5798
- }
5799
- ) });
5790
+ var DEFAULT_ROWS = 3;
5791
+ var MIN_ROWS = 2;
5792
+ var REM_PER_ROW = 1.5;
5793
+ var VERTICAL_PADDING_REM = 1;
5794
+ var TextAreaInput = ({ value, onChange, placeholder = "", className = "", variant = "default", status = "default", bordered, borderColor, rows }) => {
5795
+ const visibleRows = Math.max(rows ?? DEFAULT_ROWS, MIN_ROWS);
5796
+ const minHeight = `${visibleRows * REM_PER_ROW + VERTICAL_PADDING_REM}rem`;
5797
+ return /* @__PURE__ */ jsx(
5798
+ BaseInput,
5799
+ {
5800
+ variant,
5801
+ status,
5802
+ bordered,
5803
+ borderColor,
5804
+ className: cn("h-auto items-start", className),
5805
+ style: { minHeight },
5806
+ children: /* @__PURE__ */ jsx(
5807
+ Textarea,
5808
+ {
5809
+ rows: visibleRows,
5810
+ placeholder,
5811
+ value,
5812
+ onChange: (ev) => onChange(ev.target.value),
5813
+ className: "h-full min-h-0 resize-y border-0 bg-transparent p-0 text-muted-foreground shadow-none focus-visible:border-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 dark:bg-transparent"
5814
+ }
5815
+ )
5816
+ }
5817
+ );
5818
+ };
5800
5819
  function renderItems(items) {
5801
5820
  return items.map((item) => {
5802
5821
  const value = typeof item === "string" ? item : item.value;
@@ -7709,25 +7728,47 @@ function useFormSubmission({
7709
7728
  if (stepId) {
7710
7729
  formDataRef.current = { ...formDataRef.current, ...stepData };
7711
7730
  }
7712
- markStepCompleted(currentStep - 1);
7713
7731
  if (!isLastStep) {
7732
+ markStepCompleted(currentStep - 1);
7714
7733
  handleNext();
7734
+ return { ok: true };
7715
7735
  } else {
7716
7736
  const fullData = { ...formDataRef.current };
7737
+ let submitData = fullData;
7717
7738
  if (schema?.parse) {
7718
- const validated = schema.parse(fullData);
7719
- await onSubmit(validated);
7720
- } else {
7721
- await onSubmit(fullData);
7739
+ const result = schema.safeParse?.(fullData);
7740
+ if (result) {
7741
+ if (!result.success) {
7742
+ return { ok: false, error: result.error, data: fullData };
7743
+ }
7744
+ submitData = result.data;
7745
+ } else {
7746
+ submitData = schema.parse(fullData);
7747
+ }
7722
7748
  }
7749
+ await onSubmit(submitData);
7750
+ markStepCompleted(currentStep - 1);
7723
7751
  reset();
7724
- formDataRef.current = {};
7752
+ formDataRef.current = defaultValues ?? {};
7753
+ return { ok: true };
7725
7754
  }
7726
7755
  },
7727
- [steps, currentStep, isLastStep, schema, onSubmit, handleNext, markStepCompleted, reset]
7756
+ [steps, currentStep, isLastStep, schema, onSubmit, handleNext, markStepCompleted, reset, defaultValues]
7728
7757
  );
7729
7758
  return { formDataRef, handleStepSubmit, getStepDefaultValues };
7730
7759
  }
7760
+ function issuePath(issue) {
7761
+ return issue.path?.map(String).join(".") ?? "";
7762
+ }
7763
+ function matchesFieldPath(path, field) {
7764
+ return path === field || path.startsWith(`${field}.`) || field.startsWith(`${path}.`);
7765
+ }
7766
+ function footerDividerClass(divider = "none") {
7767
+ if (divider === false || divider === "none") return "border-t-0";
7768
+ if (divider === "dashed") return "border-t border-dashed border-border";
7769
+ if (divider === "dotted") return "border-t border-dotted border-border";
7770
+ return "border-t border-solid border-border";
7771
+ }
7731
7772
  function WizardForm({
7732
7773
  steps,
7733
7774
  schema,
@@ -7745,7 +7786,9 @@ function WizardForm({
7745
7786
  bordered,
7746
7787
  className,
7747
7788
  classNames,
7748
- footerSlot
7789
+ footerSlot,
7790
+ footerDivider = "none",
7791
+ footerDividerClassName
7749
7792
  }) {
7750
7793
  const nav = useStepNavigation({
7751
7794
  steps,
@@ -7765,6 +7808,15 @@ function WizardForm({
7765
7808
  });
7766
7809
  const currentStepConfig = nav.currentStepConfig;
7767
7810
  const stepDefaults = getStepDefaultValues(currentStepConfig.id);
7811
+ const pendingIssuesRef = useRef(null);
7812
+ const getIssueStepIndex = (issue) => {
7813
+ const path = issuePath(issue);
7814
+ if (!path) return nav.currentStep - 1;
7815
+ const stepIndex = steps.findIndex(
7816
+ (step) => step.fields?.some((field) => matchesFieldPath(path, field))
7817
+ );
7818
+ return stepIndex >= 0 ? stepIndex : nav.currentStep - 1;
7819
+ };
7768
7820
  const stepSchema = useMemo(() => {
7769
7821
  if (currentStepConfig.schema) return currentStepConfig.schema;
7770
7822
  if (currentStepConfig.fields && schema?.pick) {
@@ -7781,16 +7833,52 @@ function WizardForm({
7781
7833
  const newDefaults = getStepDefaultValues(currentStepConfig.id);
7782
7834
  form.reset(newDefaults);
7783
7835
  }, [nav.currentStep, currentStepConfig.id]);
7836
+ useEffect(() => {
7837
+ const pending = pendingIssuesRef.current;
7838
+ if (!pending || pending.stepIndex !== nav.currentStep - 1) return;
7839
+ pendingIssuesRef.current = null;
7840
+ pending.issues.forEach((issue, index) => {
7841
+ const name = issuePath(issue) || "root";
7842
+ form.setError(
7843
+ name,
7844
+ { type: "validate", message: issue.message ?? "Invalid value" },
7845
+ { shouldFocus: index === 0 }
7846
+ );
7847
+ });
7848
+ }, [nav.currentStep, currentStepConfig.id, form]);
7784
7849
  const handleSubmit = async (data) => {
7785
7850
  onStepComplete?.(nav.currentStep - 1, data);
7786
- await handleStepSubmit(data);
7851
+ const result = await handleStepSubmit(data);
7852
+ if (result.ok === true) return;
7853
+ const issues = Array.isArray(result.error?.issues) ? result.error.issues : [];
7854
+ if (issues.length === 0) {
7855
+ throw result.error;
7856
+ }
7857
+ const targetStepIndex = getIssueStepIndex(issues[0]);
7858
+ const targetIssues = issues.filter((issue) => getIssueStepIndex(issue) === targetStepIndex);
7859
+ if (targetStepIndex === nav.currentStep - 1) {
7860
+ pendingIssuesRef.current = { stepIndex: targetStepIndex, issues: targetIssues };
7861
+ const pending = pendingIssuesRef.current;
7862
+ pendingIssuesRef.current = null;
7863
+ pending.issues.forEach((issue, index) => {
7864
+ const name = issuePath(issue) || "root";
7865
+ form.setError(
7866
+ name,
7867
+ { type: "validate", message: issue.message ?? "Invalid value" },
7868
+ { shouldFocus: index === 0 }
7869
+ );
7870
+ });
7871
+ return;
7872
+ }
7873
+ pendingIssuesRef.current = { stepIndex: targetStepIndex, issues: targetIssues };
7874
+ nav.goToStep(targetStepIndex + 1);
7787
7875
  };
7788
7876
  return /* @__PURE__ */ jsxs(
7789
7877
  "div",
7790
7878
  {
7791
7879
  "data-najm-wizard-form": "true",
7792
7880
  "data-najm-dialog-actions": "content",
7793
- className: cn("flex w-full flex-col gap-4", classNames?.root, className),
7881
+ className: cn("flex min-h-full w-full flex-col gap-4", classNames?.root, className),
7794
7882
  children: [
7795
7883
  showHeader && /* @__PURE__ */ jsx(
7796
7884
  StepsHeader,
@@ -7807,7 +7895,7 @@ function WizardForm({
7807
7895
  {
7808
7896
  id: `step-${currentStepConfig.id}`,
7809
7897
  onSubmit: form.handleSubmit(handleSubmit),
7810
- className: cn("flex flex-col gap-4", classNames?.step),
7898
+ className: cn("min-h-0 flex-1 overflow-y-auto pb-4 flex flex-col gap-4", classNames?.step),
7811
7899
  autoComplete: "off",
7812
7900
  children: [
7813
7901
  currentStepConfig.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: currentStepConfig.description }),
@@ -7815,13 +7903,25 @@ function WizardForm({
7815
7903
  ]
7816
7904
  }
7817
7905
  ) }) }),
7818
- showFooter && /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between", classNames?.footer), children: [
7819
- /* @__PURE__ */ jsx("div", { children: !nav.isFirstStep && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: nav.handlePrevious, children: previousLabel }) }),
7820
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7821
- footerSlot,
7822
- nav.isLastStep ? /* @__PURE__ */ jsx(Button, { type: "submit", form: `step-${currentStepConfig.id}`, children: submitLabel }) : /* @__PURE__ */ jsx(Button, { type: "submit", form: `step-${currentStepConfig.id}`, children: nextLabel })
7823
- ] })
7824
- ] })
7906
+ showFooter && /* @__PURE__ */ jsxs(
7907
+ "div",
7908
+ {
7909
+ "data-najm-wizard-footer": "true",
7910
+ className: cn(
7911
+ "sticky bottom-0 z-10 mt-auto flex shrink-0 items-center justify-between bg-background/95 pt-3 backdrop-blur",
7912
+ footerDividerClass(footerDivider),
7913
+ footerDividerClassName,
7914
+ classNames?.footer
7915
+ ),
7916
+ children: [
7917
+ /* @__PURE__ */ jsx("div", { children: !nav.isFirstStep && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: nav.handlePrevious, children: previousLabel }) }),
7918
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7919
+ footerSlot,
7920
+ nav.isLastStep ? /* @__PURE__ */ jsx(Button, { type: "submit", form: `step-${currentStepConfig.id}`, children: submitLabel }) : /* @__PURE__ */ jsx(Button, { type: "submit", form: `step-${currentStepConfig.id}`, children: nextLabel })
7921
+ ] })
7922
+ ]
7923
+ }
7924
+ )
7825
7925
  ]
7826
7926
  }
7827
7927
  );
@@ -9277,7 +9377,7 @@ function NTableJson() {
9277
9377
  if (viewMode !== "json") return null;
9278
9378
  return /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col min-h-0 overflow-hidden", children: renderJson?.() ?? /* @__PURE__ */ jsx(NajmScroll, { axis: "both", className: "h-full min-h-0 rounded-md border border-border bg-muted/40", children: /* @__PURE__ */ jsx("pre", { className: "p-4 font-mono text-xs leading-relaxed text-foreground", children: formatJsonValue(jsonValue) }) }) });
9279
9379
  }
9280
- var DEFAULT_ROWS = 6;
9380
+ var DEFAULT_ROWS2 = 6;
9281
9381
  function NTableHeaderSkeleton() {
9282
9382
  const filters = useTableStore.use.filters();
9283
9383
  const showViewToggle = useTableStore.use.showViewToggle();
@@ -9302,7 +9402,7 @@ function NTableHeaderSkeleton() {
9302
9402
  ] })
9303
9403
  ] });
9304
9404
  }
9305
- function NTableLoadingSkeleton({ rows = DEFAULT_ROWS }) {
9405
+ function NTableLoadingSkeleton({ rows = DEFAULT_ROWS2 }) {
9306
9406
  const columns = useTableStore.use.columns();
9307
9407
  const showCheckbox = useTableStore.use.showCheckbox();
9308
9408
  const headerClassName = useTableStore.use.headerClassName();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-kit",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "type": "module",
5
5
  "description": "Reusable React UI component package for Najm framework",
6
6
  "main": "./dist/index.mjs",