@sjolystinnovation/app-kit 0.7.0 → 0.8.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 CHANGED
@@ -613,6 +613,24 @@ toast passes through, and so do the error states of `toast.promise`.
613
613
  import { toast } from "@sjolystinnovation/app-kit/toast";
614
614
  ```
615
615
 
616
+ ### `passwordSchema(strings?)`
617
+
618
+ From `@sjolystinnovation/app-kit/validation`. A zod schema for a new password, with the rules the APIs
619
+ enforce through ASP.NET Identity: at least 8 characters, with a lowercase letter, an uppercase letter
620
+ and a digit. No symbol is needed. Identity counts only ASCII, so the letters are A to Z and the digits
621
+ 0 to 9, and "Å" does not count as uppercase. Identity sets no upper limit. An app whose API does adds
622
+ it with `.max()`. A failing password gets one issue per rule it breaks, each with its message from
623
+ `strings`. Missing messages fall back to `defaultPasswordStrings`, which are English.
624
+
625
+ ```ts
626
+ import { passwordSchema } from "@sjolystinnovation/app-kit/validation";
627
+
628
+ const result = passwordSchema({ uppercase: "Ta med en stor bokstav fra A til Z." }).safeParse(newPassword);
629
+ const gargeRule = passwordSchema().max(128, "Be at most 128 characters long.");
630
+ ```
631
+
632
+ `PASSWORD_MIN_LENGTH` holds the minimum, for hint text.
633
+
616
634
  ## Theming
617
635
 
618
636
  The components draw every colour, corner radius and label size from theme variables with a light
@@ -680,6 +698,7 @@ with it and has to set the ones it uses itself.
680
698
 
681
699
  - Next.js 16, React 19, next-auth 4.24 or later and axios 1.20 or later, as peer dependencies the
682
700
  app installs itself.
701
+ - For `@sjolystinnovation/app-kit/validation`: zod 4, as an optional peer dependency.
683
702
  - For `@sjolystinnovation/app-kit/toast`: sonner 2, as an optional peer dependency. Install a single
684
703
  copy, because two copies keep two separate toast stores.
685
704
  - `jsonwebtoken` comes with the package. `@sjolystinnovation/app-kit/auth` uses it on the server.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjolystinnovation/app-kit",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Building blocks for Next.js apps that sign in against a JWT API through next-auth",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Sjølyst Innovation AS",
@@ -24,6 +24,7 @@
24
24
  "./server": "./src/server/index.ts",
25
25
  "./ui": "./src/ui/index.ts",
26
26
  "./toast": "./src/toast/index.ts",
27
+ "./validation": "./src/validation/index.ts",
27
28
  "./styles.css": "./src/styles.css"
28
29
  },
29
30
  "files": [
@@ -44,11 +45,15 @@
44
45
  "next-auth": "^4.24",
45
46
  "react": "^19",
46
47
  "react-dom": "^19",
47
- "sonner": "^2"
48
+ "sonner": "^2",
49
+ "zod": "^4"
48
50
  },
49
51
  "peerDependenciesMeta": {
50
52
  "sonner": {
51
53
  "optional": true
54
+ },
55
+ "zod": {
56
+ "optional": true
52
57
  }
53
58
  },
54
59
  "devDependencies": {
@@ -67,7 +72,8 @@
67
72
  "react-dom": "^19.3.0",
68
73
  "sonner": "^2.0.8",
69
74
  "typescript": "^6.0.3",
70
- "vitest": "^5.0.3"
75
+ "vitest": "^5.0.3",
76
+ "zod": "^4.6.5"
71
77
  },
72
78
  "keywords": [
73
79
  "next.js",
@@ -0,0 +1,2 @@
1
+ export { PASSWORD_MIN_LENGTH, defaultPasswordStrings, passwordSchema } from "./password";
2
+ export type { PasswordStrings } from "./password";
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ import { withDefaults, type TextOverrides } from "../ui/withDefaults";
3
+
4
+ /** The fewest characters a password may have. */
5
+ export const PASSWORD_MIN_LENGTH = 8;
6
+
7
+ /** The message for each password rule that fails. */
8
+ export interface PasswordStrings {
9
+ tooShort: string;
10
+ lowercase: string;
11
+ uppercase: string;
12
+ digit: string;
13
+ }
14
+
15
+ export const defaultPasswordStrings: PasswordStrings = {
16
+ tooShort: `Use at least ${PASSWORD_MIN_LENGTH} characters.`,
17
+ lowercase: "Include a lowercase letter from a to z.",
18
+ uppercase: "Include an uppercase letter from A to Z.",
19
+ digit: "Include a digit from 0 to 9.",
20
+ };
21
+
22
+ /**
23
+ * A zod schema for a new password, with the rules the APIs enforce through ASP.NET Identity. Identity
24
+ * counts only ASCII letters and digits, so "Å" is neither an uppercase nor a lowercase letter here.
25
+ * Length counts UTF-16 code units, as Identity does. There is no upper limit, because Identity has
26
+ * none. An app whose API sets one adds it with `.max()`. A failing password gets one issue per rule
27
+ * it breaks.
28
+ */
29
+ export function passwordSchema(strings?: TextOverrides<PasswordStrings>) {
30
+ const text = withDefaults(defaultPasswordStrings, strings);
31
+ return z
32
+ .string()
33
+ .min(PASSWORD_MIN_LENGTH, text.tooShort)
34
+ .regex(/[a-z]/, text.lowercase)
35
+ .regex(/[A-Z]/, text.uppercase)
36
+ .regex(/[0-9]/, text.digit);
37
+ }