@karni/ui 0.3.1 → 0.4.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.
Files changed (127) hide show
  1. package/README.md +712 -72
  2. package/dist/index.cjs +5 -5
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.js +8660 -4725
  5. package/dist/index.js.map +1 -1
  6. package/dist/manifest.json +18 -1
  7. package/dist/script-BVJ9BbPm.cjs +2 -0
  8. package/dist/script-BVJ9BbPm.cjs.map +1 -0
  9. package/dist/script-D1fn4Dm4.js +6 -0
  10. package/dist/script-D1fn4Dm4.js.map +1 -0
  11. package/dist/src/auth/AuthGuard.d.ts +69 -4
  12. package/dist/src/auth/LoginForm.d.ts +41 -7
  13. package/dist/src/auth/engine.d.ts +15 -13
  14. package/dist/src/auth/provider.d.ts +10 -10
  15. package/dist/src/auth/storage.d.ts +1 -1
  16. package/dist/src/auth/types.d.ts +3 -3
  17. package/dist/src/components/brand/karni-mark.d.ts +4 -4
  18. package/dist/src/components/chart/data-chart.d.ts +66 -11
  19. package/dist/src/components/empty-state/empty-state.d.ts +22 -2
  20. package/dist/src/components/file-upload/FileUpload.d.ts +67 -3
  21. package/dist/src/components/layout/layout.d.ts +104 -9
  22. package/dist/src/components/layout/route-focus.d.ts +43 -0
  23. package/dist/src/components/modal/modal.d.ts +49 -8
  24. package/dist/src/components/page/page.d.ts +36 -5
  25. package/dist/src/components/primitives/button.d.ts +1 -0
  26. package/dist/src/components/primitives/checkbox.d.ts +4 -1
  27. package/dist/src/components/primitives/dropdown-menu.d.ts +1 -1
  28. package/dist/src/components/primitives/icons.d.ts +9 -1
  29. package/dist/src/components/primitives/input.d.ts +4 -1
  30. package/dist/src/components/primitives/label.d.ts +4 -1
  31. package/dist/src/components/primitives/select.d.ts +37 -6
  32. package/dist/src/components/primitives/skeleton.d.ts +4 -1
  33. package/dist/src/components/primitives/switch.d.ts +4 -1
  34. package/dist/src/components/primitives/tabs.d.ts +2 -2
  35. package/dist/src/components/primitives/textarea.d.ts +4 -1
  36. package/dist/src/components/resource/ResourceDetail.d.ts +3 -3
  37. package/dist/src/components/resource/ResourceForm.d.ts +4 -3
  38. package/dist/src/components/resource/ResourceSelect.d.ts +3 -1
  39. package/dist/src/components/resource/ResourceTable.d.ts +9 -3
  40. package/dist/src/components/resource/audit-fields.d.ts +1 -1
  41. package/dist/src/components/resource/detail/ResourceDetail.d.ts +109 -11
  42. package/dist/src/components/resource/empty-value.d.ts +17 -0
  43. package/dist/src/components/resource/form/ResourceForm.d.ts +65 -6
  44. package/dist/src/components/resource/form/field-inputs.d.ts +15 -1
  45. package/dist/src/components/resource/form/form-states.d.ts +87 -0
  46. package/dist/src/components/resource/form/prepare.d.ts +3 -3
  47. package/dist/src/components/resource/form/server-errors.d.ts +14 -0
  48. package/dist/src/components/resource/history/ChangeHistory.d.ts +46 -4
  49. package/dist/src/components/resource/import/ResourceImport.d.ts +73 -7
  50. package/dist/src/components/resource/nouns.d.ts +12 -0
  51. package/dist/src/components/resource/query-state.d.ts +20 -0
  52. package/dist/src/components/resource/resource-names.d.ts +23 -0
  53. package/dist/src/components/resource/select/ResourceSelect.d.ts +39 -8
  54. package/dist/src/components/resource/select/select-states.d.ts +68 -0
  55. package/dist/src/components/resource/select/use-option-pages.d.ts +37 -0
  56. package/dist/src/components/resource/select/venue-backend.d.ts +144 -0
  57. package/dist/src/components/resource/table/BulkActionsBar.d.ts +62 -5
  58. package/dist/src/components/resource/table/CardList.d.ts +31 -0
  59. package/dist/src/components/resource/table/ConfirmDialog.d.ts +28 -0
  60. package/dist/src/components/resource/table/ExportButtons.d.ts +2 -2
  61. package/dist/src/components/resource/table/FilterBar.d.ts +3 -3
  62. package/dist/src/components/resource/table/PaginationControls.d.ts +15 -3
  63. package/dist/src/components/resource/table/ResourceTable.d.ts +79 -14
  64. package/dist/src/components/resource/table/RowCard.d.ts +31 -0
  65. package/dist/src/components/resource/table/TableView.d.ts +29 -0
  66. package/dist/src/components/resource/table/ViewOptions.d.ts +19 -0
  67. package/dist/src/components/resource/table/access.d.ts +12 -0
  68. package/dist/src/components/resource/table/derive-columns.d.ts +2 -2
  69. package/dist/src/components/resource/table/grid-navigation.d.ts +23 -0
  70. package/dist/src/components/resource/table/layout.d.ts +37 -0
  71. package/dist/src/components/resource/table/preferences.d.ts +12 -0
  72. package/dist/src/components/resource/table/slots.d.ts +41 -0
  73. package/dist/src/components/resource/table/states.d.ts +91 -0
  74. package/dist/src/components/resource/table/story-fixtures.d.ts +25 -0
  75. package/dist/src/components/resource/table/structural-columns.d.ts +25 -0
  76. package/dist/src/components/resource/table/table-icons.d.ts +16 -0
  77. package/dist/src/components/resource/table/table-utils.d.ts +27 -3
  78. package/dist/src/components/resource/table/url-state.d.ts +67 -0
  79. package/dist/src/components/resource/table/use-container-width.d.ts +10 -0
  80. package/dist/src/components/resource/testapp-fixtures.d.ts +78 -0
  81. package/dist/src/components/stat/stat.d.ts +36 -6
  82. package/dist/src/components/toast/toast-store.d.ts +1 -1
  83. package/dist/src/components/toast/toaster.d.ts +39 -6
  84. package/dist/src/components/toast/use-toast.d.ts +1 -1
  85. package/dist/src/hooks/keys.d.ts +4 -4
  86. package/dist/src/hooks/multipart.d.ts +9 -9
  87. package/dist/src/hooks/params.d.ts +1 -1
  88. package/dist/src/hooks/types.d.ts +3 -3
  89. package/dist/src/hooks/useChangeHistory.d.ts +3 -2
  90. package/dist/src/hooks/useResourceImport.d.ts +2 -2
  91. package/dist/src/hooks/useResourceMutations.d.ts +2 -2
  92. package/dist/src/hooks/useUnsavedChangesGuard.d.ts +31 -0
  93. package/dist/src/index.d.ts +49 -31
  94. package/dist/src/lib/roles.d.ts +4 -4
  95. package/dist/src/lib/variants.d.ts +1 -1
  96. package/dist/src/primitives/registry.d.ts +81 -0
  97. package/dist/src/schema/client.d.ts +10 -10
  98. package/dist/src/schema/parse.d.ts +2 -2
  99. package/dist/src/schema/provider.d.ts +10 -3
  100. package/dist/src/schema/types.d.ts +18 -9
  101. package/dist/src/schema/useSchema.d.ts +5 -1
  102. package/dist/src/states/ErrorState.d.ts +43 -0
  103. package/dist/src/states/PendingButton.d.ts +16 -0
  104. package/dist/src/states/describe-error.d.ts +25 -0
  105. package/dist/src/states/hooks.d.ts +10 -0
  106. package/dist/src/theme/script.d.ts +12 -0
  107. package/dist/src/theme/theme.d.ts +45 -0
  108. package/dist/styles.css +174 -13
  109. package/dist/tailwind-preset.cjs +2 -2
  110. package/dist/tailwind-preset.cjs.map +1 -1
  111. package/dist/tailwind-preset.d.ts +83 -1
  112. package/dist/tailwind-preset.js +181 -62
  113. package/dist/tailwind-preset.js.map +1 -1
  114. package/dist/tailwind-v4.css +31 -0
  115. package/dist/vite-plugin.cjs +2 -0
  116. package/dist/vite-plugin.cjs.map +1 -0
  117. package/dist/vite-plugin.d.ts +19 -0
  118. package/dist/vite-plugin.js +16 -0
  119. package/dist/vite-plugin.js.map +1 -0
  120. package/eslint-plugin/index.js +4 -0
  121. package/eslint-plugin/rules/max-page-lines.js +1 -1
  122. package/eslint-plugin/rules/no-deep-imports.js +1 -1
  123. package/eslint-plugin/rules/no-direct-http.js +1 -1
  124. package/eslint-plugin/rules/no-raw-elements.js +1 -1
  125. package/eslint-plugin/rules/no-style-literals.js +222 -0
  126. package/eslint-plugin/rules.json +10 -4
  127. package/package.json +22 -19
@@ -1 +1 @@
1
- {"version":3,"file":"tailwind-preset.js","sources":["../tailwind-preset.ts"],"sourcesContent":["/**\n * Tailwind (v3) preset for @karni/ui consumers.\n *\n * Usage (tailwind.config.ts of the consuming app):\n *\n * ```ts\n * import karniPreset from \"@karni/ui/tailwind-preset\";\n *\n * export default {\n * presets: [karniPreset],\n * content: [\n * \"./src/**\\/*.{ts,tsx}\",\n * \"./node_modules/@karni/ui/dist/**\\/*.js\",\n * ],\n * };\n * ```\n *\n * Also import \"@karni/ui/styles.css\" once (it defines the --karni-* tokens).\n * Tailwind v4 users: see the README recipe (@source into dist) instead.\n */\nimport type { Config } from \"tailwindcss\";\n\nconst karniPreset = {\n // `as const` keeps the literal type so the preset stays assignable to\n // Config[\"presets\"] when consumers spread it into their own config.\n darkMode: \"class\" as const,\n theme: {\n extend: {\n colors: {\n background: \"hsl(var(--karni-background) / <alpha-value>)\",\n foreground: \"hsl(var(--karni-foreground) / <alpha-value>)\",\n card: {\n DEFAULT: \"hsl(var(--karni-card) / <alpha-value>)\",\n foreground: \"hsl(var(--karni-card-foreground) / <alpha-value>)\",\n },\n popover: {\n DEFAULT: \"hsl(var(--karni-popover) / <alpha-value>)\",\n foreground: \"hsl(var(--karni-popover-foreground) / <alpha-value>)\",\n },\n primary: {\n DEFAULT: \"hsl(var(--karni-primary) / <alpha-value>)\",\n foreground: \"hsl(var(--karni-primary-foreground) / <alpha-value>)\",\n },\n secondary: {\n DEFAULT: \"hsl(var(--karni-secondary) / <alpha-value>)\",\n foreground: \"hsl(var(--karni-secondary-foreground) / <alpha-value>)\",\n },\n muted: {\n DEFAULT: \"hsl(var(--karni-muted) / <alpha-value>)\",\n foreground: \"hsl(var(--karni-muted-foreground) / <alpha-value>)\",\n },\n accent: {\n DEFAULT: \"hsl(var(--karni-accent) / <alpha-value>)\",\n foreground: \"hsl(var(--karni-accent-foreground) / <alpha-value>)\",\n },\n destructive: {\n DEFAULT: \"hsl(var(--karni-destructive) / <alpha-value>)\",\n foreground: \"hsl(var(--karni-destructive-foreground) / <alpha-value>)\",\n },\n border: \"hsl(var(--karni-border) / <alpha-value>)\",\n input: \"hsl(var(--karni-input) / <alpha-value>)\",\n ring: \"hsl(var(--karni-ring) / <alpha-value>)\",\n chart: {\n \"1\": \"hsl(var(--karni-chart-1) / <alpha-value>)\",\n \"2\": \"hsl(var(--karni-chart-2) / <alpha-value>)\",\n \"3\": \"hsl(var(--karni-chart-3) / <alpha-value>)\",\n \"4\": \"hsl(var(--karni-chart-4) / <alpha-value>)\",\n \"5\": \"hsl(var(--karni-chart-5) / <alpha-value>)\",\n },\n },\n borderColor: {\n // A bare `border` class uses the token, never Tailwind's default gray.\n DEFAULT: \"hsl(var(--karni-border) / <alpha-value>)\",\n },\n borderRadius: {\n lg: \"var(--karni-radius)\",\n md: \"calc(var(--karni-radius) - 2px)\",\n sm: \"calc(var(--karni-radius) - 4px)\",\n },\n },\n },\n} satisfies Omit<Config, \"content\">;\n\nexport default karniPreset;\n"],"names":["karniPreset"],"mappings":"AAsBA,MAAMA,IAAc;AAAA;AAAA;AAAA,EAGlB,UAAU;AAAA,EACV,OAAO;AAAA,IACL,QAAQ;AAAA,MACN,QAAQ;AAAA,QACN,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,MAAM;AAAA,UACJ,SAAS;AAAA,UACT,YAAY;AAAA,QAAA;AAAA,QAEd,SAAS;AAAA,UACP,SAAS;AAAA,UACT,YAAY;AAAA,QAAA;AAAA,QAEd,SAAS;AAAA,UACP,SAAS;AAAA,UACT,YAAY;AAAA,QAAA;AAAA,QAEd,WAAW;AAAA,UACT,SAAS;AAAA,UACT,YAAY;AAAA,QAAA;AAAA,QAEd,OAAO;AAAA,UACL,SAAS;AAAA,UACT,YAAY;AAAA,QAAA;AAAA,QAEd,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,YAAY;AAAA,QAAA;AAAA,QAEd,aAAa;AAAA,UACX,SAAS;AAAA,UACT,YAAY;AAAA,QAAA;AAAA,QAEd,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,MAAM;AAAA,QACN,OAAO;AAAA,UACL,GAAK;AAAA,UACL,GAAK;AAAA,UACL,GAAK;AAAA,UACL,GAAK;AAAA,UACL,GAAK;AAAA,QAAA;AAAA,MACP;AAAA,MAEF,aAAa;AAAA;AAAA,QAEX,SAAS;AAAA,MAAA;AAAA,MAEX,cAAc;AAAA,QACZ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MAAA;AAAA,IACN;AAAA,EACF;AAEJ;"}
1
+ {"version":3,"file":"tailwind-preset.js","names":["color","name","SPACE_STEPS","spacing","scale","step","FONT_SIZES","fontSize","size","leading","karniPreset"],"sources":["../tailwind-preset.ts"],"sourcesContent":["/**\n * Tailwind preset for @karni/ui consumers (Tailwind 3, and 4 through @config).\n *\n * Usage (tailwind.config.ts of the consuming app):\n *\n * ```ts\n * import karniPreset from \"@karni/ui/tailwind-preset\";\n *\n * export default {\n * presets: [karniPreset],\n * content: [\n * \"./src/**\\/*.{ts,tsx}\",\n * \"./node_modules/@karni/ui/dist/**\\/*.js\",\n * ],\n * };\n * ```\n *\n * Also import \"@karni/ui/styles.css\" once (it defines the --karni-* tokens).\n * Tailwind v4 loads the same config through `@config` in CSS, next to\n * `@import \"@karni/ui/tailwind-v4.css\"` (see the README).\n */\nimport type { Config } from \"tailwindcss\";\n\n/**\n * A colour token as a Tailwind colour. Wrapping it in color-mix with\n * `<alpha-value>` keeps opacity modifiers (`bg-primary/50`) working in\n * Tailwind 3 as well as 4, for any colour format the token holds.\n */\nfunction color(name: string): string {\n return `color-mix(in srgb, var(--karni-color-${name}) calc(<alpha-value> * 100%), transparent)`;\n}\n\n/** The spacing scale: each step reads --karni-space-<n>, else n × --karni-space-unit. */\nexport const SPACE_STEPS = [\n 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48,\n 52, 56, 60, 64, 72, 80, 96, 128,\n] as const;\n\nfunction spacing(): Record<string, string> {\n const scale: Record<string, string> = {\n 0: \"0px\",\n px: \"var(--karni-space-px, 1px)\",\n };\n for (const step of SPACE_STEPS) {\n const name = String(step).replace(\".\", \"-\");\n scale[String(step)] =\n `var(--karni-space-${name}, calc(var(--karni-space-unit, 0.25rem) * ${step}))`;\n }\n return scale;\n}\n\nconst FONT_SIZES = {\n xs: [\"0.75rem\", \"1rem\"],\n sm: [\"0.875rem\", \"1.25rem\"],\n base: [\"1rem\", \"1.5rem\"],\n lg: [\"1.125rem\", \"1.75rem\"],\n xl: [\"1.25rem\", \"1.75rem\"],\n \"2xl\": [\"1.5rem\", \"2rem\"],\n} as const;\n\nfunction fontSize(): Record<string, [string, { lineHeight: string }]> {\n return Object.fromEntries(\n Object.entries(FONT_SIZES).map(([name, [size, leading]]) => [\n name,\n [\n `var(--karni-font-size-${name}, ${size})`,\n { lineHeight: `var(--karni-font-leading-${name}, ${leading})` },\n ],\n ]),\n );\n}\n\nconst karniPreset = {\n // `as const` keeps the literal type so the preset stays assignable to\n // Config[\"presets\"] when consumers spread it into their own config.\n darkMode: \"class\" as const,\n theme: {\n extend: {\n colors: {\n background: color(\"background\"),\n foreground: color(\"foreground\"),\n card: { DEFAULT: color(\"card\"), foreground: color(\"card-foreground\") },\n popover: { DEFAULT: color(\"popover\"), foreground: color(\"popover-foreground\") },\n primary: {\n DEFAULT: color(\"primary\"),\n foreground: color(\"primary-foreground\"),\n hover: color(\"primary-hover\"),\n },\n secondary: {\n DEFAULT: color(\"secondary\"),\n foreground: color(\"secondary-foreground\"),\n hover: color(\"secondary-hover\"),\n },\n muted: {\n DEFAULT: color(\"muted\"),\n foreground: color(\"muted-foreground\"),\n subtle: color(\"muted-subtle\"),\n },\n accent: { DEFAULT: color(\"accent\"), foreground: color(\"accent-foreground\") },\n destructive: {\n DEFAULT: color(\"destructive\"),\n foreground: color(\"destructive-foreground\"),\n hover: color(\"destructive-hover\"),\n subtle: color(\"destructive-subtle\"),\n border: color(\"destructive-border\"),\n },\n border: color(\"border\"),\n input: color(\"input\"),\n ring: color(\"ring\"),\n overlay: color(\"overlay\"),\n chart: {\n \"1\": color(\"chart-1\"),\n \"2\": color(\"chart-2\"),\n \"3\": color(\"chart-3\"),\n \"4\": color(\"chart-4\"),\n \"5\": color(\"chart-5\"),\n },\n },\n borderColor: {\n // A bare `border` class uses the token, never Tailwind's default gray.\n DEFAULT: color(\"border\"),\n },\n spacing: spacing(),\n borderRadius: {\n DEFAULT: \"var(--karni-radius-sm, 0.25rem)\",\n sm: \"var(--karni-radius-sm, 0.25rem)\",\n md: \"var(--karni-radius-md, 0.375rem)\",\n lg: \"var(--karni-radius-lg, 0.5rem)\",\n xl: \"var(--karni-radius-xl, 0.75rem)\",\n full: \"var(--karni-radius-full, 9999px)\",\n },\n fontFamily: {\n sans: \"var(--karni-font-sans, ui-sans-serif, system-ui, sans-serif)\",\n mono: \"var(--karni-font-mono, ui-monospace, monospace)\",\n },\n fontSize: fontSize(),\n fontWeight: {\n normal: \"var(--karni-font-weight-normal, 400)\",\n medium: \"var(--karni-font-weight-medium, 500)\",\n semibold: \"var(--karni-font-weight-semibold, 600)\",\n bold: \"var(--karni-font-weight-bold, 700)\",\n },\n lineHeight: { none: \"var(--karni-font-leading-none, 1)\" },\n textUnderlineOffset: { \"4\": \"var(--karni-font-underline-offset, 4px)\" },\n letterSpacing: {\n tight: \"var(--karni-font-tracking-tight, -0.025em)\",\n wide: \"var(--karni-font-tracking-wide, 0.025em)\",\n },\n boxShadow: {\n sm: \"var(--karni-shadow-sm, 0 1px 3px 0 rgb(0 0 0 / 0.1))\",\n DEFAULT: \"var(--karni-shadow, 0 1px 3px 0 rgb(0 0 0 / 0.1))\",\n md: \"var(--karni-shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1))\",\n lg: \"var(--karni-shadow-lg, 0 10px 15px -3px rgb(0 0 0 / 0.1))\",\n },\n borderWidth: {\n DEFAULT: \"var(--karni-border-width, 1px)\",\n \"2\": \"var(--karni-border-width-2, 2px)\",\n },\n divideWidth: { DEFAULT: \"var(--karni-border-width, 1px)\" },\n outlineWidth: { DEFAULT: \"var(--karni-border-width, 1px)\" },\n ringWidth: {\n \"1\": \"var(--karni-ring-width-1, 1px)\",\n \"2\": \"var(--karni-ring-width, 2px)\",\n },\n ringOffsetWidth: { \"2\": \"var(--karni-ring-offset-width, 2px)\" },\n zIndex: {\n raised: \"var(--karni-z-raised, 10)\",\n drawer: \"var(--karni-z-drawer, 40)\",\n popover: \"var(--karni-z-popover, 50)\",\n modal: \"var(--karni-z-modal, 50)\",\n toast: \"var(--karni-z-toast, 100)\",\n },\n opacity: {\n \"50\": \"var(--karni-opacity-50, 0.5)\",\n \"60\": \"var(--karni-opacity-60, 0.6)\",\n \"70\": \"var(--karni-opacity-70, 0.7)\",\n \"90\": \"var(--karni-opacity-90, 0.9)\",\n },\n transitionDuration: { DEFAULT: \"var(--karni-duration, 150ms)\" },\n transitionTimingFunction: { DEFAULT: \"var(--karni-ease, cubic-bezier(0.4, 0, 0.2, 1))\" },\n animation: {\n pulse: \"pulse var(--karni-duration-pulse, 2s) cubic-bezier(0.4, 0, 0.6, 1) infinite\",\n spin: \"spin var(--karni-duration-spin, 1s) linear infinite\",\n },\n },\n },\n} satisfies Omit<Config, \"content\">;\n\nexport default karniPreset;\n"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /* @karni/ui under Tailwind 4: import once, after Tailwind itself.
2
+ *
3
+ * @import "tailwindcss";
4
+ * @import "@karni/ui/tailwind-v4.css";
5
+ * @config "../tailwind.config.ts"; (the @karni/ui preset)
6
+ *
7
+ * Tailwind 3 projects do not import this file: Tailwind 3 rejects an
8
+ * `@layer base` outside its own stylesheet, and needs nothing here.
9
+ *
10
+ * A bare `border` uses the --karni-color-border token, as under Tailwind 3, whose
11
+ * preflight applies the preset's default border color. Tailwind 4 defaults
12
+ * borders to currentColor. In the base layer this rule sits below every
13
+ * utility, so `border-input`, `border-destructive` and the rest still win;
14
+ * `:root` lifts it just above Tailwind's own reset. */
15
+ @layer base {
16
+ :root *,
17
+ :root ::before,
18
+ :root ::after,
19
+ :root ::backdrop,
20
+ :root ::file-selector-button {
21
+ border-color: var(--karni-color-border);
22
+ }
23
+ }
24
+
25
+ /* Tailwind 4 reads transition timing from its own theme variables rather
26
+ * than the preset; point them at the motion tokens (which drop to 0ms under
27
+ * prefers-reduced-motion). */
28
+ @theme {
29
+ --default-transition-duration: var(--karni-duration);
30
+ --default-transition-timing-function: var(--karni-ease);
31
+ }
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./script-BVJ9BbPm.cjs");function t(){return{name:`karni-theme`,transformIndexHtml:()=>[{tag:`script`,children:e.t,injectTo:`head-prepend`}]}}exports.karniTheme=t;
2
+ //# sourceMappingURL=vite-plugin.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin.cjs","names":["KARNI_THEME_SCRIPT","require_script","karniTheme"],"sources":["../vite-plugin.ts"],"sourcesContent":["/**\n * `@karni/ui/vite`: what a Vite app needs from @karni/ui at build time.\n *\n * import { karniTheme } from \"@karni/ui/vite\";\n * export default defineConfig({ plugins: [react(), karniTheme()] });\n *\n * Typed structurally, so it needs no `vite` types of its own.\n */\nimport { KARNI_THEME_SCRIPT } from \"./src/theme/script\";\n\n/** One tag Vite's `transformIndexHtml` adds to index.html. */\nexport interface KarniHtmlTag {\n tag: \"script\";\n children: string;\n injectTo: \"head-prepend\";\n}\n\n/** The plugin object `karniTheme()` returns (a Vite `Plugin`). */\nexport interface KarniThemePlugin {\n name: \"karni-theme\";\n transformIndexHtml: () => KarniHtmlTag[];\n}\n\n/**\n * Puts KARNI_THEME_SCRIPT first in index.html's <head>, so the page paints\n * in the user's theme (stored, else the system's) before React starts.\n * Pair it with <ThemeProvider> around the app. Escape hatch: leave the\n * plugin out and inline KARNI_THEME_SCRIPT yourself (another storage key,\n * another framework).\n */\nexport function karniTheme(): KarniThemePlugin {\n return {\n name: \"karni-theme\",\n transformIndexHtml: () => [\n { tag: \"script\", children: KARNI_THEME_SCRIPT, injectTo: \"head-prepend\" },\n ],\n };\n}\n"],"mappings":"A,4GA8BA,SAAgBE,GAA+B,CAC7C,MAAO,CACL,KAAM,cACN,uBAA0B,CACxB,CAAE,IAAK,SAAU,SAAUF,EAAAA,EAAoB,SAAU,cAAe,CAC1E,CACF,CACF,C"}
@@ -0,0 +1,19 @@
1
+ /** One tag Vite's `transformIndexHtml` adds to index.html. */
2
+ export interface KarniHtmlTag {
3
+ tag: "script";
4
+ children: string;
5
+ injectTo: "head-prepend";
6
+ }
7
+ /** The plugin object `karniTheme()` returns (a Vite `Plugin`). */
8
+ export interface KarniThemePlugin {
9
+ name: "karni-theme";
10
+ transformIndexHtml: () => KarniHtmlTag[];
11
+ }
12
+ /**
13
+ * Puts KARNI_THEME_SCRIPT first in index.html's <head>, so the page paints
14
+ * in the user's theme (stored, else the system's) before React starts.
15
+ * Pair it with <ThemeProvider> around the app. Escape hatch: leave the
16
+ * plugin out and inline KARNI_THEME_SCRIPT yourself (another storage key,
17
+ * another framework).
18
+ */
19
+ export declare function karniTheme(): KarniThemePlugin;
@@ -0,0 +1,16 @@
1
+ import { t as e } from "./script-D1fn4Dm4.js";
2
+ //#region vite-plugin.ts
3
+ function t() {
4
+ return {
5
+ name: "karni-theme",
6
+ transformIndexHtml: () => [{
7
+ tag: "script",
8
+ children: e,
9
+ injectTo: "head-prepend"
10
+ }]
11
+ };
12
+ }
13
+ //#endregion
14
+ export { t as karniTheme };
15
+
16
+ //# sourceMappingURL=vite-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin.js","names":["KARNI_THEME_SCRIPT","karniTheme"],"sources":["../vite-plugin.ts"],"sourcesContent":["/**\n * `@karni/ui/vite`: what a Vite app needs from @karni/ui at build time.\n *\n * import { karniTheme } from \"@karni/ui/vite\";\n * export default defineConfig({ plugins: [react(), karniTheme()] });\n *\n * Typed structurally, so it needs no `vite` types of its own.\n */\nimport { KARNI_THEME_SCRIPT } from \"./src/theme/script\";\n\n/** One tag Vite's `transformIndexHtml` adds to index.html. */\nexport interface KarniHtmlTag {\n tag: \"script\";\n children: string;\n injectTo: \"head-prepend\";\n}\n\n/** The plugin object `karniTheme()` returns (a Vite `Plugin`). */\nexport interface KarniThemePlugin {\n name: \"karni-theme\";\n transformIndexHtml: () => KarniHtmlTag[];\n}\n\n/**\n * Puts KARNI_THEME_SCRIPT first in index.html's <head>, so the page paints\n * in the user's theme (stored, else the system's) before React starts.\n * Pair it with <ThemeProvider> around the app. Escape hatch: leave the\n * plugin out and inline KARNI_THEME_SCRIPT yourself (another storage key,\n * another framework).\n */\nexport function karniTheme(): KarniThemePlugin {\n return {\n name: \"karni-theme\",\n transformIndexHtml: () => [\n { tag: \"script\", children: KARNI_THEME_SCRIPT, injectTo: \"head-prepend\" },\n ],\n };\n}\n"],"mappings":";;AA8BA,SAAgBC,IAA+B;CAC7C,OAAO;EACL,MAAM;EACN,0BAA0B,CACxB;GAAE,KAAK;GAAU,UAAUD;GAAoB,UAAU;EAAe,CAC1E;CACF;AACF"}
@@ -24,6 +24,7 @@ import maxPageLines from "./rules/max-page-lines.js";
24
24
  import noDeepImports from "./rules/no-deep-imports.js";
25
25
  import noDirectHttp from "./rules/no-direct-http.js";
26
26
  import noRawElements from "./rules/no-raw-elements.js";
27
+ import noStyleLiterals from "./rules/no-style-literals.js";
27
28
 
28
29
  const plugin = {
29
30
  meta: {
@@ -35,6 +36,9 @@ const plugin = {
35
36
  "max-page-lines": maxPageLines,
36
37
  "no-deep-imports": noDeepImports,
37
38
  "no-direct-http": noDirectHttp,
39
+ // Not in `recommended`: a project with its own design system uses its own
40
+ // palette. @karni/ui enables it for its own source; projects may opt in.
41
+ "no-style-literals": noStyleLiterals,
38
42
  },
39
43
  // Filled below so the config entries can reference the plugin object itself.
40
44
  configs: {},
@@ -34,7 +34,7 @@ export default {
34
34
  messages: {
35
35
  tooLong:
36
36
  "Page file is {{count}} physical lines (max {{max}}). Past the cap you are rebuilding " +
37
- "something @karni/ui ships — compose Page/ResourceTable/ResourceForm/ResourceDetail and " +
37
+ "something @karni/ui ships. Compose Page/ResourceTable/ResourceForm/ResourceDetail and " +
38
38
  "their props, or move logic into hooks/components. " +
39
39
  "Opt out only with an explained disable: /* eslint-disable karni/max-page-lines -- <reason> */",
40
40
  },
@@ -30,7 +30,7 @@ export default {
30
30
  messages: {
31
31
  deepImport:
32
32
  'Deep import "{{source}}" reaches into @karni/ui internals. Import from the public ' +
33
- 'barrel "@karni/ui" and compose/extend the exported component — vendoring an internal ' +
33
+ 'barrel "@karni/ui" and compose/extend the exported component. Vendoring an internal ' +
34
34
  "copy forks it. " +
35
35
  "Opt out only with an explained disable: " +
36
36
  "// eslint-disable-next-line karni/no-deep-imports -- <reason>",
@@ -72,7 +72,7 @@ export default {
72
72
  fetch:
73
73
  "Direct fetch() outside the api client directory. Use the generated typed hooks " +
74
74
  "(useResourceList/useResourceItem/useResourceMutations) or the KarniClient from " +
75
- "@karni/ui — raw HTTP lives only under the allowed api/ paths. " +
75
+ "@karni/ui. Raw HTTP lives only under the allowed api/ paths. " +
76
76
  "Opt out only with an explained disable: " +
77
77
  "// eslint-disable-next-line karni/no-direct-http -- <reason>",
78
78
  axios:
@@ -37,7 +37,7 @@ export default {
37
37
  ],
38
38
  messages: {
39
39
  rawElement:
40
- "Raw <{{element}}> is Karni boilerplate — compose {{replacement}} from @karni/ui instead. " +
40
+ "Raw <{{element}}> is Karni boilerplate. Compose {{replacement}} from @karni/ui instead. " +
41
41
  "Opt out only with an explained disable: " +
42
42
  "// eslint-disable-next-line karni/no-raw-elements -- <reason>",
43
43
  },
@@ -0,0 +1,222 @@
1
+ /**
2
+ * karni/no-style-literals — every visual value resolves from a design token.
3
+ *
4
+ * @karni/ui's preset maps Tailwind's scales (colours, spacing, type, radius,
5
+ * shadow, stacking, opacity, motion) onto --karni-* custom properties, so a
6
+ * project retheming those properties retheming every component. A class
7
+ * outside those scales compiles to a literal the theme cannot reach: an
8
+ * arbitrary value (`w-[13px]`, `bg-[#fff]`), Tailwind's palette
9
+ * (`bg-slate-500`), an opacity modifier on a colour (`bg-primary/90`), a step
10
+ * the spacing scale does not have, or a literal in an inline style.
11
+ *
12
+ * The rule checks the whitespace-separated class tokens of every string in
13
+ * the file and the values of inline `style={{ ... }}` objects. Computed style
14
+ * values (data-driven widths, percentages) are allowed: only literals fail.
15
+ */
16
+ import { docsUrl } from "../lib/docs.js";
17
+
18
+ /** The preset's spacing steps (keep in sync with SPACE_STEPS in tailwind-preset.ts). */
19
+ const SPACE_STEPS = new Set([
20
+ "0",
21
+ "px",
22
+ "0.5",
23
+ "1",
24
+ "1.5",
25
+ "2",
26
+ "2.5",
27
+ "3",
28
+ "3.5",
29
+ "4",
30
+ "5",
31
+ "6",
32
+ "7",
33
+ "8",
34
+ "9",
35
+ "10",
36
+ "11",
37
+ "12",
38
+ "14",
39
+ "16",
40
+ "20",
41
+ "24",
42
+ "28",
43
+ "32",
44
+ "36",
45
+ "40",
46
+ "44",
47
+ "48",
48
+ "52",
49
+ "56",
50
+ "60",
51
+ "64",
52
+ "72",
53
+ "80",
54
+ "96",
55
+ "128",
56
+ ]);
57
+
58
+ const SPACING_UTILITY =
59
+ /^-?(?:p[xytrblse]?|m[xytrblse]?|gap(?:-[xy])?|space-[xy]|w|h|size|min-w|min-h|max-w|max-h|inset(?:-[xy])?|top|right|bottom|left|start|end|translate-[xy]|scroll-[mp][xytrblse]?|basis|indent)-(\d+(?:\.\d+)?)$/;
60
+
61
+ const PALETTE =
62
+ "slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|black|white";
63
+ const COLOR_UTILITY =
64
+ "bg|text|border(?:-[xytrblse])?|ring|ring-offset|outline|fill|stroke|from|via|to|divide|placeholder|decoration|accent|caret|shadow";
65
+ const PALETTE_CLASS = new RegExp(`^(?:${COLOR_UTILITY})-(?:${PALETTE})(?:-\\d+)?(?:/\\d+)?$`);
66
+ const OPACITY_MODIFIER = new RegExp(`^(?:${COLOR_UTILITY})-[a-z][\\w-]*/\\d+$`);
67
+
68
+ /** Classes whose value comes from Tailwind's default theme, not a --karni-* token. */
69
+ const UNMAPPED = [
70
+ [
71
+ /^max-w-(?:xs|sm|md|lg|xl|[2-7]xl|prose|screen-\w+)$/,
72
+ "a named max-width (use a spacing step, e.g. max-w-96)",
73
+ ],
74
+ [/^text-(?:[3-9]xl)$/, "a type size outside the --karni-font-size-* scale (xs to 2xl)"],
75
+ [/^-?z-\d+$/, "a numeric z-index (use z-raised, z-drawer, z-popover, z-modal or z-toast)"],
76
+ [/^opacity-(?!(?:0|50|60|70|90|100)$)\d+$/, "an opacity outside the --karni-opacity-* steps"],
77
+ [/^(?:duration|delay)-\d+$/, "a literal duration (transitions read --karni-duration)"],
78
+ [
79
+ /^rounded(?:-[tblrse]{1,2})?-(?:2xl|3xl)$/,
80
+ "a radius outside --karni-radius-* (sm to xl, full)",
81
+ ],
82
+ [/^shadow-(?:xl|2xl|inner)$/, "a shadow outside --karni-shadow-* (sm, md, lg)"],
83
+ [/^font-(?:thin|extralight|light|extrabold|black)$/, "a weight outside --karni-font-weight-*"],
84
+ [/^leading-(?!none$)[\w.]+$/, "a line height outside the type scale"],
85
+ [/^tracking-(?:tighter|wider|widest|normal)$/, "letter spacing outside --karni-font-tracking-*"],
86
+ [
87
+ /^(?:border(?:-[xytrblse])?|ring-offset|outline)-(?!0$|2$)\d+$/,
88
+ "a line width outside --karni-border-width / --karni-ring-*",
89
+ ],
90
+ [/^ring-(?!0$|1$|2$)\d+$/, "a ring width outside --karni-ring-width*"],
91
+ [/^underline-offset-(?!4$)\d+$/, "an underline offset outside --karni-font-underline-offset"],
92
+ [/^divide-[xy](?:-\d+)?$/, "a divider whose width Tailwind 4 hard-codes (use [&>*+*]:border-t)"],
93
+ ];
94
+
95
+ const HEX = /#[0-9a-fA-F]{3,8}\b/;
96
+ const LITERAL_IN_VALUE = /\d|#|\b(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|color)\(/;
97
+
98
+ /** The base utility of a class token: variants (`hover:`, `md:`, `data-[x]:`) and `!` stripped. */
99
+ function utilityOf(token) {
100
+ let depth = 0;
101
+ let start = 0;
102
+ for (let i = 0; i < token.length; i += 1) {
103
+ const ch = token[i];
104
+ if (ch === "[") depth += 1;
105
+ else if (ch === "]") depth -= 1;
106
+ else if (ch === ":" && depth === 0) start = i + 1;
107
+ }
108
+ return token.slice(start).replace(/^!/, "");
109
+ }
110
+
111
+ /** Why `utility` is a literal, or null when it resolves from a token. */
112
+ function problemOf(utility) {
113
+ const arbitrary = /^-?[\w-]*\[(.+)\]$/.exec(utility);
114
+ if (arbitrary) {
115
+ const value = arbitrary[1]
116
+ .replace(/var\(--[\w-]+(?:,[^)]*)?\)/g, "")
117
+ .replace(/\d+fr\b/g, "")
118
+ .replace(/\b(?:min|max|fit)-content\b/g, "");
119
+ return LITERAL_IN_VALUE.test(value) ? "an arbitrary literal value" : null;
120
+ }
121
+ if (PALETTE_CLASS.test(utility)) return "a Tailwind palette colour (use a --karni-color-* token)";
122
+ if (OPACITY_MODIFIER.test(utility)) {
123
+ return "an opacity modifier on a colour (use a token such as bg-primary-hover or bg-muted-subtle)";
124
+ }
125
+ const spacing = SPACING_UTILITY.exec(utility);
126
+ if (spacing && !SPACE_STEPS.has(spacing[1]))
127
+ return "a step the --karni-space-* scale does not have";
128
+ for (const [pattern, why] of UNMAPPED) if (pattern.test(utility)) return why;
129
+ return null;
130
+ }
131
+
132
+ function checkClasses(context, node, text) {
133
+ if (HEX.test(text)) {
134
+ context.report({
135
+ node,
136
+ messageId: "literal",
137
+ data: { value: HEX.exec(text)[0], why: "a hex colour" },
138
+ });
139
+ }
140
+ for (const token of text.split(/\s+/)) {
141
+ if (!token) continue;
142
+ const why = problemOf(utilityOf(token));
143
+ if (why) context.report({ node, messageId: "literal", data: { value: token, why } });
144
+ }
145
+ }
146
+
147
+ function literalStyleValue(node) {
148
+ if (node.type === "Literal") {
149
+ if (typeof node.value === "number") return node.value !== 0;
150
+ return (
151
+ typeof node.value === "string" &&
152
+ LITERAL_IN_VALUE.test(node.value.replace(/var\(--[\w-]+\)/g, ""))
153
+ );
154
+ }
155
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
156
+ return LITERAL_IN_VALUE.test(node.quasis[0].value.cooked ?? "");
157
+ }
158
+ return false;
159
+ }
160
+
161
+ export default {
162
+ meta: {
163
+ type: "problem",
164
+ docs: {
165
+ url: docsUrl("no-style-literals"),
166
+ description:
167
+ "Every visual value resolves from a --karni-* token: no arbitrary literal values, palette colours, colour opacity modifiers or literal inline styles.",
168
+ },
169
+ schema: [],
170
+ messages: {
171
+ literal:
172
+ '"{{value}}" is {{why}}: the theme cannot reach it. Use a class from the @karni/ui preset ' +
173
+ "(it resolves from --karni-* tokens), or add a token to styles.css and the preset. " +
174
+ "Opt out only with an explained disable: " +
175
+ "// eslint-disable-next-line karni/no-style-literals -- <reason>",
176
+ },
177
+ },
178
+
179
+ create(context) {
180
+ return {
181
+ Literal(node) {
182
+ if (typeof node.value !== "string") return;
183
+ if (
184
+ node.parent?.type === "ImportDeclaration" ||
185
+ node.parent?.type === "ExportNamedDeclaration"
186
+ )
187
+ return;
188
+ if (node.parent?.type === "Property" && node.parent.key === node) return;
189
+ if (insideStyleObject(node)) return;
190
+ checkClasses(context, node, node.value);
191
+ },
192
+ TemplateElement(node) {
193
+ if (insideStyleObject(node)) return;
194
+ checkClasses(context, node, node.value.cooked ?? "");
195
+ },
196
+ JSXAttribute(node) {
197
+ if (node.name.name !== "style") return;
198
+ const expression =
199
+ node.value?.type === "JSXExpressionContainer" ? node.value.expression : null;
200
+ if (expression?.type !== "ObjectExpression") return;
201
+ for (const property of expression.properties) {
202
+ if (property.type === "Property" && literalStyleValue(property.value)) {
203
+ context.report({
204
+ node: property.value,
205
+ messageId: "literal",
206
+ data: { value: context.sourceCode.getText(property), why: "a literal inline style" },
207
+ });
208
+ }
209
+ }
210
+ },
211
+ };
212
+ },
213
+ };
214
+
215
+ /** Inline style values are checked by the JSXAttribute visitor, not as class strings. */
216
+ function insideStyleObject(node) {
217
+ for (let current = node.parent; current; current = current.parent) {
218
+ if (current.type === "JSXAttribute") return current.name.name === "style";
219
+ if (current.type === "JSXElement" || current.type === "Program") return false;
220
+ }
221
+ return false;
222
+ }
@@ -8,19 +8,25 @@
8
8
  {
9
9
  "id": "karni/max-page-lines",
10
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.",
11
+ "why": "Pages compose @karni/ui components. A long page is rebuilding something Karni ships.",
12
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
13
  },
14
14
  {
15
15
  "id": "karni/no-deep-imports",
16
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.",
17
+ "why": "Only the package root is public API (docs/stability.md). Internals change without notice, and a vendored copy forks the component.",
18
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
19
  },
20
20
  {
21
21
  "id": "karni/no-direct-http",
22
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>`."
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, and keep transports under the api/ directory. Opt out only with `// eslint-disable-next-line karni/no-direct-http -- <reason>`."
25
+ },
26
+ {
27
+ "id": "karni/no-style-literals",
28
+ "title": "A class or inline style holds a literal value instead of a design token",
29
+ "why": "@karni/ui's preset resolves every colour, spacing step, radius, type size, shadow, z-index, opacity and duration from a --karni-* custom property, so one stylesheet (or `karni ui detect`) rethemes every component. A literal (`w-[13px]`, `bg-slate-500`, `bg-primary/90`, `style={{ color: \"#333\" }}`) is out of the theme's reach.",
30
+ "fix": "Use a class from the @karni/ui preset (`bg-primary-hover`, `bg-muted-subtle`, `max-w-96`, `z-popover`), or add the token to styles.css and the preset. @karni/ui enables this rule for its own source. A project opts in with `\"karni/no-style-literals\": \"error\"`. Opt out only with `// eslint-disable-next-line karni/no-style-literals -- <reason>`."
25
31
  }
26
32
  ]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@karni/ui",
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.",
3
+ "version": "0.4.0",
4
+ "description": "React components for Karni: schema-driven tables, forms, detail views and auth. Karni is an open-source Django + React framework for building with AI coding agents. Your agent writes only your domain. The API, admin, permissions and React screens come from a package you upgrade, not code it rewrites in each project. One rule set covers Claude Code, Cursor and Copilot. The build fails when an agent hand-writes what Karni ships.",
5
5
  "keywords": [
6
6
  "react",
7
7
  "django",
@@ -48,11 +48,17 @@
48
48
  "require": "./dist/index.cjs"
49
49
  },
50
50
  "./styles.css": "./dist/styles.css",
51
+ "./tailwind-v4.css": "./dist/tailwind-v4.css",
51
52
  "./tailwind-preset": {
52
53
  "types": "./dist/tailwind-preset.d.ts",
53
54
  "import": "./dist/tailwind-preset.js",
54
55
  "require": "./dist/tailwind-preset.cjs"
55
56
  },
57
+ "./vite": {
58
+ "types": "./dist/vite-plugin.d.ts",
59
+ "import": "./dist/vite-plugin.js",
60
+ "require": "./dist/vite-plugin.cjs"
61
+ },
56
62
  "./manifest.json": "./dist/manifest.json",
57
63
  "./eslint-plugin": "./eslint-plugin/index.js",
58
64
  "./package.json": "./package.json"
@@ -62,7 +68,7 @@
62
68
  "types": "./dist/src/index.d.ts",
63
69
  "scripts": {
64
70
  "dev": "vite build --watch",
65
- "build": "tsc --noEmit && vite build && node scripts/generate-manifest.mjs",
71
+ "build": "tsc --noEmit && eslint src --max-warnings 0 && vite build && node scripts/generate-manifest.mjs",
66
72
  "test": "vitest run",
67
73
  "test:watch": "vitest",
68
74
  "lint": "eslint .",
@@ -78,10 +84,10 @@
78
84
  ]
79
85
  },
80
86
  "peerDependencies": {
81
- "eslint": ">=9.0.0",
82
87
  "@hookform/resolvers": "^3.0.0",
83
88
  "@tanstack/react-query": "^5.0.0",
84
89
  "@tanstack/react-table": "^8.0.0",
90
+ "eslint": ">=9.0.0",
85
91
  "react": "^18.0.0 || ^19.0.0",
86
92
  "react-dom": "^18.0.0 || ^19.0.0",
87
93
  "react-hook-form": "^7.0.0",
@@ -105,38 +111,39 @@
105
111
  "devDependencies": {
106
112
  "@eslint/js": "^9.39.5",
107
113
  "@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",
114
+ "@storybook/react": "^10.6.3",
115
+ "@storybook/react-vite": "^10.6.3",
116
+ "@tailwindcss/postcss": "^4.3.3",
111
117
  "@tanstack/react-query": "^5.104.3",
112
118
  "@tanstack/react-table": "^8.20.5",
113
119
  "@testing-library/jest-dom": "^6.5.0",
114
120
  "@testing-library/react": "^16.0.1",
115
121
  "@testing-library/user-event": "^14.6.8",
116
- "@types/node": "^20.16.0",
122
+ "@types/node": "^24.0.0",
117
123
  "@types/react": "^18.3.5",
118
124
  "@types/react-dom": "^18.3.0",
119
- "@vitejs/plugin-react": "^4.7.0",
120
- "autoprefixer": "^10.4.20",
125
+ "@vitejs/plugin-react": "^6.1.2",
126
+ "axe-core": "^4.14.0",
121
127
  "eslint": "^9.39.5",
122
128
  "eslint-config-prettier": "^9.1.0",
123
129
  "eslint-plugin-react-hooks": "^5.2.0",
124
130
  "globals": "^15.15.0",
125
131
  "jsdom": "^25.0.0",
126
132
  "msw": "^2.4.4",
127
- "msw-storybook-addon": "^2.0.3",
133
+ "msw-storybook-addon": "^3.0.3",
134
+ "playwright": "^1.64.1",
128
135
  "postcss": "^8.5.30",
129
136
  "prettier": "^3.9.10",
130
137
  "react": "^18.3.1",
131
138
  "react-dom": "^18.3.1",
132
139
  "react-hook-form": "^7.90.0",
133
- "storybook": "^8.3.0",
134
- "tailwindcss": "^3.4.10",
140
+ "storybook": "^10.6.3",
141
+ "tailwindcss": "^4.3.3",
135
142
  "typescript": "^5.6.2",
136
143
  "typescript-eslint": "^8.71.2",
137
- "vite": "^6.4.4",
144
+ "vite": "^8.3.4",
138
145
  "vite-plugin-dts": "^5.1.2",
139
- "vitest": "^4.1.12",
146
+ "vitest": "^5.0.5",
140
147
  "zod": "^3.23.8"
141
148
  },
142
149
  "peerDependenciesMeta": {
@@ -147,9 +154,5 @@
147
154
  "publishConfig": {
148
155
  "access": "public",
149
156
  "provenance": true
150
- },
151
- "overrides": {
152
- "postcss-selector-parser": "^7.1.7",
153
- "uuid": "^11.1.1"
154
157
  }
155
158
  }