@karni/ui 0.0.0-stage → 0.3.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 (96) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +288 -2
  3. package/dist/index.cjs +9 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.js +4764 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/manifest.json +85 -0
  8. package/dist/mockServiceWorker.js +361 -0
  9. package/dist/src/auth/AuthGuard.d.ts +12 -0
  10. package/dist/src/auth/LoginForm.d.ts +29 -0
  11. package/dist/src/auth/engine.d.ts +99 -0
  12. package/dist/src/auth/provider.d.ts +43 -0
  13. package/dist/src/auth/storage.d.ts +26 -0
  14. package/dist/src/auth/types.d.ts +32 -0
  15. package/dist/src/auth/usePermission.d.ts +7 -0
  16. package/dist/src/components/brand/karni-mark.d.ts +21 -0
  17. package/dist/src/components/brand/mark-paths.d.ts +8 -0
  18. package/dist/src/components/chart/data-chart.d.ts +74 -0
  19. package/dist/src/components/empty-state/empty-state.d.ts +20 -0
  20. package/dist/src/components/file-upload/FileUpload.d.ts +44 -0
  21. package/dist/src/components/layout/layout.d.ts +55 -0
  22. package/dist/src/components/modal/modal.d.ts +45 -0
  23. package/dist/src/components/page/page.d.ts +29 -0
  24. package/dist/src/components/primitives/button.d.ts +44 -0
  25. package/dist/src/components/primitives/checkbox.d.ts +5 -0
  26. package/dist/src/components/primitives/dropdown-menu.d.ts +24 -0
  27. package/dist/src/components/primitives/icons.d.ts +11 -0
  28. package/dist/src/components/primitives/input.d.ts +4 -0
  29. package/dist/src/components/primitives/label.d.ts +5 -0
  30. package/dist/src/components/primitives/select.d.ts +63 -0
  31. package/dist/src/components/primitives/skeleton.d.ts +4 -0
  32. package/dist/src/components/primitives/switch.d.ts +5 -0
  33. package/dist/src/components/primitives/tabs.d.ts +10 -0
  34. package/dist/src/components/primitives/textarea.d.ts +4 -0
  35. package/dist/src/components/resource/ResourceDetail.d.ts +7 -0
  36. package/dist/src/components/resource/ResourceForm.d.ts +7 -0
  37. package/dist/src/components/resource/ResourceSelect.d.ts +7 -0
  38. package/dist/src/components/resource/ResourceTable.d.ts +11 -0
  39. package/dist/src/components/resource/audit-fields.d.ts +17 -0
  40. package/dist/src/components/resource/detail/ResourceDetail.d.ts +30 -0
  41. package/dist/src/components/resource/displayLabel.d.ts +12 -0
  42. package/dist/src/components/resource/form/ResourceForm.d.ts +50 -0
  43. package/dist/src/components/resource/form/field-inputs.d.ts +20 -0
  44. package/dist/src/components/resource/form/fixtures.d.ts +24 -0
  45. package/dist/src/components/resource/form/prepare.d.ts +54 -0
  46. package/dist/src/components/resource/history/ChangeHistory.d.ts +18 -0
  47. package/dist/src/components/resource/import/ResourceImport.d.ts +18 -0
  48. package/dist/src/components/resource/select/ResourceSelect.d.ts +36 -0
  49. package/dist/src/components/resource/select/use-debounced-value.d.ts +2 -0
  50. package/dist/src/components/resource/table/BulkActionsBar.d.ts +20 -0
  51. package/dist/src/components/resource/table/ExportButtons.d.ts +17 -0
  52. package/dist/src/components/resource/table/FilterBar.d.ts +29 -0
  53. package/dist/src/components/resource/table/PaginationControls.d.ts +19 -0
  54. package/dist/src/components/resource/table/ResourceTable.d.ts +72 -0
  55. package/dist/src/components/resource/table/demo-fixtures.d.ts +25 -0
  56. package/dist/src/components/resource/table/derive-columns.d.ts +33 -0
  57. package/dist/src/components/resource/table/table-utils.d.ts +24 -0
  58. package/dist/src/components/resource/table/use-debounced-value.d.ts +1 -0
  59. package/dist/src/components/stat/stat.d.ts +24 -0
  60. package/dist/src/components/toast/toast-store.d.ts +36 -0
  61. package/dist/src/components/toast/toaster.d.ts +33 -0
  62. package/dist/src/components/toast/use-toast.d.ts +24 -0
  63. package/dist/src/hooks/keys.d.ts +24 -0
  64. package/dist/src/hooks/multipart.d.ts +44 -0
  65. package/dist/src/hooks/params.d.ts +25 -0
  66. package/dist/src/hooks/types.d.ts +41 -0
  67. package/dist/src/hooks/useChangeHistory.d.ts +29 -0
  68. package/dist/src/hooks/useResourceImport.d.ts +107 -0
  69. package/dist/src/hooks/useResourceItem.d.ts +4 -0
  70. package/dist/src/hooks/useResourceList.d.ts +4 -0
  71. package/dist/src/hooks/useResourceMutations.d.ts +13 -0
  72. package/dist/src/index.d.ts +95 -0
  73. package/dist/src/lib/cn.d.ts +8 -0
  74. package/dist/src/lib/roles.d.ts +45 -0
  75. package/dist/src/lib/variants.d.ts +33 -0
  76. package/dist/src/schema/client.d.ts +90 -0
  77. package/dist/src/schema/parse.d.ts +101 -0
  78. package/dist/src/schema/provider.d.ts +13 -0
  79. package/dist/src/schema/types.d.ts +84 -0
  80. package/dist/src/schema/useSchema.d.ts +17 -0
  81. package/dist/src/schema/zod.d.ts +4 -0
  82. package/dist/styles.css +87 -0
  83. package/dist/tailwind-preset.cjs +2 -0
  84. package/dist/tailwind-preset.cjs.map +1 -0
  85. package/dist/tailwind-preset.d.ts +58 -0
  86. package/dist/tailwind-preset.js +64 -0
  87. package/dist/tailwind-preset.js.map +1 -0
  88. package/eslint-plugin/index.js +72 -0
  89. package/eslint-plugin/lib/docs.js +11 -0
  90. package/eslint-plugin/lib/glob.js +51 -0
  91. package/eslint-plugin/rules/max-page-lines.js +65 -0
  92. package/eslint-plugin/rules/no-deep-imports.js +61 -0
  93. package/eslint-plugin/rules/no-direct-http.js +128 -0
  94. package/eslint-plugin/rules/no-raw-elements.js +65 -0
  95. package/eslint-plugin/rules.json +26 -0
  96. package/package.json +153 -4
@@ -0,0 +1,128 @@
1
+ /**
2
+ * karni/no-direct-http — direct HTTP/WS clients (fetch, axios,
3
+ * XMLHttpRequest, new WebSocket) are banned outside the generated client
4
+ * directory (allowPatterns, default **-slash-api). Data access goes through
5
+ * the generated typed hooks and the KarniClient from @karni/ui.
6
+ */
7
+ import { docsUrl } from "../lib/docs.js";
8
+ import { matchesAny } from "../lib/glob.js";
9
+
10
+ const DEFAULT_ALLOW_PATTERNS = ["**/api/**"];
11
+ const GLOBAL_OBJECTS = new Set(["window", "globalThis", "self"]);
12
+
13
+ /** True when `name` resolves to a declared (shadowing) binding in scope. */
14
+ function isShadowed(sourceCode, node, name) {
15
+ let scope = sourceCode.getScope(node);
16
+ while (scope) {
17
+ const variable = scope.set.get(name);
18
+ if (variable && variable.defs.length > 0) return true;
19
+ scope = scope.upper;
20
+ }
21
+ return false;
22
+ }
23
+
24
+ /** Matches `fetch(...)`, `window.fetch(...)`, `globalThis.fetch(...)`. */
25
+ function calleeIsGlobal(sourceCode, callee, name) {
26
+ if (callee.type === "Identifier" && callee.name === name) {
27
+ return !isShadowed(sourceCode, callee, name);
28
+ }
29
+ if (
30
+ callee.type === "MemberExpression" &&
31
+ !callee.computed &&
32
+ callee.object.type === "Identifier" &&
33
+ GLOBAL_OBJECTS.has(callee.object.name) &&
34
+ callee.property.type === "Identifier" &&
35
+ callee.property.name === name
36
+ ) {
37
+ return true;
38
+ }
39
+ return false;
40
+ }
41
+
42
+ function checkAxiosSource(context, sourceNode) {
43
+ if (!sourceNode || sourceNode.type !== "Literal") return;
44
+ if (typeof sourceNode.value !== "string") return;
45
+ if (sourceNode.value !== "axios" && !sourceNode.value.startsWith("axios/")) return;
46
+ context.report({ node: sourceNode, messageId: "axios" });
47
+ }
48
+
49
+ export default {
50
+ meta: {
51
+ type: "problem",
52
+ docs: {
53
+ url: docsUrl("no-direct-http"),
54
+ description:
55
+ "Ban fetch/axios/XMLHttpRequest/new WebSocket outside the generated api client directory: use the generated typed hooks or KarniClient.",
56
+ },
57
+ schema: [
58
+ {
59
+ type: "object",
60
+ properties: {
61
+ allowPatterns: {
62
+ type: "array",
63
+ items: { type: "string" },
64
+ description:
65
+ "Glob patterns of file paths where direct HTTP is allowed (the generated client's home).",
66
+ },
67
+ },
68
+ additionalProperties: false,
69
+ },
70
+ ],
71
+ messages: {
72
+ fetch:
73
+ "Direct fetch() outside the api client directory. Use the generated typed hooks " +
74
+ "(useResourceList/useResourceItem/useResourceMutations) or the KarniClient from " +
75
+ "@karni/ui — raw HTTP lives only under the allowed api/ paths. " +
76
+ "Opt out only with an explained disable: " +
77
+ "// eslint-disable-next-line karni/no-direct-http -- <reason>",
78
+ axios:
79
+ "Do not add axios. Karni already ships an HTTP layer: the generated typed hooks and " +
80
+ "createKarniClient from @karni/ui. " +
81
+ "Opt out only with an explained disable: " +
82
+ "// eslint-disable-next-line karni/no-direct-http -- <reason>",
83
+ xhr:
84
+ "XMLHttpRequest outside the api client directory. Use the generated typed hooks or " +
85
+ "the KarniClient from @karni/ui instead. " +
86
+ "Opt out only with an explained disable: " +
87
+ "// eslint-disable-next-line karni/no-direct-http -- <reason>",
88
+ websocket:
89
+ "new WebSocket() outside the api client directory. Wrap realtime transports in the " +
90
+ "api/ layer next to the KarniClient so every consumer shares one implementation. " +
91
+ "Opt out only with an explained disable: " +
92
+ "// eslint-disable-next-line karni/no-direct-http -- <reason>",
93
+ },
94
+ },
95
+
96
+ create(context) {
97
+ const options = context.options[0] ?? {};
98
+ const allowPatterns = options.allowPatterns ?? DEFAULT_ALLOW_PATTERNS;
99
+ if (matchesAny(context.filename, allowPatterns)) {
100
+ return {};
101
+ }
102
+ const { sourceCode } = context;
103
+ return {
104
+ CallExpression(node) {
105
+ if (calleeIsGlobal(sourceCode, node.callee, "fetch")) {
106
+ context.report({ node, messageId: "fetch" });
107
+ return;
108
+ }
109
+ if (node.callee.type === "Identifier" && node.callee.name === "require") {
110
+ checkAxiosSource(context, node.arguments[0]);
111
+ }
112
+ },
113
+ NewExpression(node) {
114
+ if (calleeIsGlobal(sourceCode, node.callee, "XMLHttpRequest")) {
115
+ context.report({ node, messageId: "xhr" });
116
+ } else if (calleeIsGlobal(sourceCode, node.callee, "WebSocket")) {
117
+ context.report({ node, messageId: "websocket" });
118
+ }
119
+ },
120
+ ImportDeclaration(node) {
121
+ checkAxiosSource(context, node.source);
122
+ },
123
+ ImportExpression(node) {
124
+ checkAxiosSource(context, node.source);
125
+ },
126
+ };
127
+ },
128
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * karni/no-raw-elements — raw data-entry/table JSX elements are banned in
3
+ * project code; @karni/ui is their only home. Each message names the
4
+ * component to compose instead.
5
+ */
6
+ import { docsUrl } from "../lib/docs.js";
7
+ import { matchesAny } from "../lib/glob.js";
8
+
9
+ const REPLACEMENTS = {
10
+ table: "ResourceTable",
11
+ form: "ResourceForm",
12
+ input: "Input (or ResourceForm, which renders the right field for you)",
13
+ select: "Select / ResourceSelect",
14
+ textarea: "Textarea (or ResourceForm)",
15
+ };
16
+
17
+ export default {
18
+ meta: {
19
+ type: "problem",
20
+ docs: {
21
+ url: docsUrl("no-raw-elements"),
22
+ description:
23
+ "Ban raw <table>/<form>/<input>/<select>/<textarea> JSX outside @karni/ui: compose the shipped components instead of rebuilding them.",
24
+ },
25
+ schema: [
26
+ {
27
+ type: "object",
28
+ properties: {
29
+ allow: {
30
+ type: "array",
31
+ items: { type: "string" },
32
+ description: "Glob patterns of file paths where raw elements are allowed.",
33
+ },
34
+ },
35
+ additionalProperties: false,
36
+ },
37
+ ],
38
+ messages: {
39
+ rawElement:
40
+ "Raw <{{element}}> is Karni boilerplate — compose {{replacement}} from @karni/ui instead. " +
41
+ "Opt out only with an explained disable: " +
42
+ "// eslint-disable-next-line karni/no-raw-elements -- <reason>",
43
+ },
44
+ },
45
+
46
+ create(context) {
47
+ const options = context.options[0] ?? {};
48
+ const allow = options.allow ?? [];
49
+ if (allow.length > 0 && matchesAny(context.filename, allow)) {
50
+ return {};
51
+ }
52
+ return {
53
+ JSXOpeningElement(node) {
54
+ if (node.name.type !== "JSXIdentifier") return;
55
+ const element = node.name.name;
56
+ if (!Object.hasOwn(REPLACEMENTS, element)) return;
57
+ context.report({
58
+ node: node.name,
59
+ messageId: "rawElement",
60
+ data: { element, replacement: REPLACEMENTS[element] },
61
+ });
62
+ },
63
+ };
64
+ },
65
+ };
@@ -0,0 +1,26 @@
1
+ [
2
+ {
3
+ "id": "karni/no-raw-elements",
4
+ "title": "Raw <table>/<form>/<input>/<select>/<textarea> in project code",
5
+ "why": "@karni/ui ships these as schema-driven components (sorting, pagination, validation, accessibility). Rebuilding them per page is the boilerplate Karni exists to remove, and the copies drift.",
6
+ "fix": "Compose the component the message names: ResourceTable for <table>, ResourceForm for <form>, Input / Select / ResourceSelect / Textarea for fields (ResourceForm renders the right field for you). Opt out only with `// eslint-disable-next-line karni/no-raw-elements -- <reason>`."
7
+ },
8
+ {
9
+ "id": "karni/max-page-lines",
10
+ "title": "A page file is longer than its cap (50 lines by default)",
11
+ "why": "Pages compose @karni/ui components; a long page is rebuilding something Karni ships.",
12
+ "fix": "Compose Page / ResourceTable / ResourceForm / ResourceDetail and their props, or move logic into hooks and components. Opt out only with `/* eslint-disable karni/max-page-lines -- <reason> */`."
13
+ },
14
+ {
15
+ "id": "karni/no-deep-imports",
16
+ "title": "An import reaches into @karni/ui internals",
17
+ "why": "Only the package root is public API (docs/stability.md); internals change without notice, and a vendored copy forks the component.",
18
+ "fix": "Import from \"@karni/ui\" and compose or extend the exported component. Opt out only with `// eslint-disable-next-line karni/no-deep-imports -- <reason>`."
19
+ },
20
+ {
21
+ "id": "karni/no-direct-http",
22
+ "title": "fetch / axios / XMLHttpRequest / WebSocket outside the api client",
23
+ "why": "The generated typed hooks and KarniClient handle auth, errors, retries and types in one place; ad-hoc HTTP calls bypass all of it.",
24
+ "fix": "Use the generated typed hooks (useResourceList / useResourceItem / useResourceMutations) or createKarniClient from @karni/ui; keep transports under the api/ directory. Opt out only with `// eslint-disable-next-line karni/no-direct-http -- <reason>`."
25
+ }
26
+ ]
package/package.json CHANGED
@@ -1,6 +1,155 @@
1
1
  {
2
2
  "name": "@karni/ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.1",
4
+ "description": "React components for the Karni Django + React framework: schema-driven resource tables, forms, detail views and auth, generated from the backend's OpenAPI document, plus themable primitives and an ESLint preset.",
5
+ "keywords": [
6
+ "react",
7
+ "django",
8
+ "django-rest-framework",
9
+ "openapi",
10
+ "typescript",
11
+ "components",
12
+ "ui",
13
+ "tanstack-query",
14
+ "react-hook-form",
15
+ "zod",
16
+ "tailwind",
17
+ "radix",
18
+ "ai-agents",
19
+ "karni"
20
+ ],
21
+ "license": "Apache-2.0",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/cbsshekhawat18-lab/karni.git",
25
+ "directory": "packages/karni-ui"
26
+ },
27
+ "homepage": "https://github.com/cbsshekhawat18-lab/karni/tree/main/packages/karni-ui#readme",
28
+ "bugs": {
29
+ "url": "https://github.com/cbsshekhawat18-lab/karni/issues"
30
+ },
31
+ "type": "module",
32
+ "sideEffects": [
33
+ "**/*.css"
34
+ ],
35
+ "files": [
36
+ "dist",
37
+ "eslint-plugin",
38
+ "!eslint-plugin/**/*.test.*",
39
+ "README.md"
40
+ ],
41
+ "engines": {
42
+ "node": ">=20"
43
+ },
44
+ "exports": {
45
+ ".": {
46
+ "types": "./dist/src/index.d.ts",
47
+ "import": "./dist/index.js",
48
+ "require": "./dist/index.cjs"
49
+ },
50
+ "./styles.css": "./dist/styles.css",
51
+ "./tailwind-preset": {
52
+ "types": "./dist/tailwind-preset.d.ts",
53
+ "import": "./dist/tailwind-preset.js",
54
+ "require": "./dist/tailwind-preset.cjs"
55
+ },
56
+ "./manifest.json": "./dist/manifest.json",
57
+ "./eslint-plugin": "./eslint-plugin/index.js",
58
+ "./package.json": "./package.json"
59
+ },
60
+ "main": "./dist/index.cjs",
61
+ "module": "./dist/index.js",
62
+ "types": "./dist/src/index.d.ts",
63
+ "scripts": {
64
+ "dev": "vite build --watch",
65
+ "build": "tsc --noEmit && vite build && node scripts/generate-manifest.mjs",
66
+ "test": "vitest run",
67
+ "test:watch": "vitest",
68
+ "lint": "eslint .",
69
+ "typecheck": "tsc --noEmit",
70
+ "format": "prettier --write .",
71
+ "format:check": "prettier --check .",
72
+ "storybook": "storybook dev -p 6006",
73
+ "build-storybook": "storybook build"
74
+ },
75
+ "msw": {
76
+ "workerDirectory": [
77
+ "public"
78
+ ]
79
+ },
80
+ "peerDependencies": {
81
+ "eslint": ">=9.0.0",
82
+ "@hookform/resolvers": "^3.0.0",
83
+ "@tanstack/react-query": "^5.0.0",
84
+ "@tanstack/react-table": "^8.0.0",
85
+ "react": "^18.0.0 || ^19.0.0",
86
+ "react-dom": "^18.0.0 || ^19.0.0",
87
+ "react-hook-form": "^7.0.0",
88
+ "zod": "^3.23.0"
89
+ },
90
+ "dependencies": {
91
+ "@eslint-community/eslint-plugin-eslint-comments": "^4.8.1",
92
+ "@radix-ui/react-checkbox": "^1.4.1",
93
+ "@radix-ui/react-dialog": "^1.3.1",
94
+ "@radix-ui/react-dropdown-menu": "^2.2.1",
95
+ "@radix-ui/react-label": "^2.1.17",
96
+ "@radix-ui/react-popover": "^1.3.1",
97
+ "@radix-ui/react-select": "^2.4.1",
98
+ "@radix-ui/react-switch": "^1.4.1",
99
+ "@radix-ui/react-tabs": "^1.2.1",
100
+ "@radix-ui/react-toast": "^1.3.1",
101
+ "@radix-ui/react-visually-hidden": "^1.1.0",
102
+ "clsx": "^2.1.1",
103
+ "tailwind-merge": "^2.5.2"
104
+ },
105
+ "devDependencies": {
106
+ "@eslint/js": "^9.39.5",
107
+ "@hookform/resolvers": "^3.9.0",
108
+ "@storybook/addon-essentials": "^8.3.0",
109
+ "@storybook/react": "^8.3.0",
110
+ "@storybook/react-vite": "^8.3.0",
111
+ "@tanstack/react-query": "^5.104.3",
112
+ "@tanstack/react-table": "^8.20.5",
113
+ "@testing-library/jest-dom": "^6.5.0",
114
+ "@testing-library/react": "^16.0.1",
115
+ "@testing-library/user-event": "^14.6.8",
116
+ "@types/node": "^20.16.0",
117
+ "@types/react": "^18.3.5",
118
+ "@types/react-dom": "^18.3.0",
119
+ "@vitejs/plugin-react": "^4.7.0",
120
+ "autoprefixer": "^10.4.20",
121
+ "eslint": "^9.39.5",
122
+ "eslint-config-prettier": "^9.1.0",
123
+ "eslint-plugin-react-hooks": "^5.2.0",
124
+ "globals": "^15.15.0",
125
+ "jsdom": "^25.0.0",
126
+ "msw": "^2.4.4",
127
+ "msw-storybook-addon": "^2.0.3",
128
+ "postcss": "^8.5.30",
129
+ "prettier": "^3.9.10",
130
+ "react": "^18.3.1",
131
+ "react-dom": "^18.3.1",
132
+ "react-hook-form": "^7.90.0",
133
+ "storybook": "^8.3.0",
134
+ "tailwindcss": "^3.4.10",
135
+ "typescript": "^5.6.2",
136
+ "typescript-eslint": "^8.71.2",
137
+ "vite": "^6.4.4",
138
+ "vite-plugin-dts": "^5.1.2",
139
+ "vitest": "^4.1.12",
140
+ "zod": "^3.23.8"
141
+ },
142
+ "peerDependenciesMeta": {
143
+ "eslint": {
144
+ "optional": true
145
+ }
146
+ },
147
+ "publishConfig": {
148
+ "access": "public",
149
+ "provenance": true
150
+ },
151
+ "overrides": {
152
+ "postcss-selector-parser": "^7.1.7",
153
+ "uuid": "^11.1.1"
154
+ }
155
+ }