@majapisoftwares/ui 6.0.0 → 6.1.2

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 (73) hide show
  1. package/AGENTS.md +27 -27
  2. package/README.md +79 -79
  3. package/THIRD_PARTY_NOTICES.md +39 -39
  4. package/components.json +25 -25
  5. package/docs/MIGRATION-0.2.md +20 -20
  6. package/docs/MIGRATION-6.0.md +33 -33
  7. package/docs/NEXT-STARTER-VALIDATION.md +14 -14
  8. package/docs/STANDARDS.md +41 -41
  9. package/docs/VALIDATION.md +11 -11
  10. package/docs/VERSIONING.md +28 -28
  11. package/docs/validation/preference-titles.md +22 -0
  12. package/package.json +88 -84
  13. package/scripts/check-standards.mjs +41 -41
  14. package/src/components/ui/accordion.tsx +82 -82
  15. package/src/components/ui/alert-dialog.tsx +187 -187
  16. package/src/components/ui/alert.tsx +77 -77
  17. package/src/components/ui/avatar.tsx +108 -108
  18. package/src/components/ui/badge.tsx +55 -55
  19. package/src/components/ui/breadcrumb.tsx +125 -125
  20. package/src/components/ui/button-group.tsx +89 -89
  21. package/src/components/ui/button.tsx +59 -59
  22. package/src/components/ui/card.tsx +101 -101
  23. package/src/components/ui/checkbox.tsx +27 -27
  24. package/src/components/ui/collapsible.tsx +21 -21
  25. package/src/components/ui/context-menu.tsx +271 -271
  26. package/src/components/ui/dialog.tsx +161 -161
  27. package/src/components/ui/dropdown-menu.tsx +271 -271
  28. package/src/components/ui/empty.tsx +105 -105
  29. package/src/components/ui/field.tsx +238 -238
  30. package/src/components/ui/hover-card.tsx +50 -50
  31. package/src/components/ui/input-group.tsx +155 -155
  32. package/src/components/ui/input.tsx +21 -21
  33. package/src/components/ui/kbd.tsx +28 -28
  34. package/src/components/ui/label.tsx +19 -19
  35. package/src/components/ui/orb.css +1002 -1002
  36. package/src/components/ui/orb.tsx +752 -752
  37. package/src/components/ui/popover.tsx +89 -89
  38. package/src/components/ui/progress.tsx +82 -82
  39. package/src/components/ui/radio-group.tsx +37 -37
  40. package/src/components/ui/scroll-area.tsx +54 -54
  41. package/src/components/ui/select.tsx +204 -204
  42. package/src/components/ui/separator.tsx +24 -24
  43. package/src/components/ui/sheet.tsx +139 -139
  44. package/src/components/ui/sidebar.tsx +1039 -1035
  45. package/src/components/ui/skeleton.tsx +15 -15
  46. package/src/components/ui/slider.tsx +53 -53
  47. package/src/components/ui/sonner.tsx +44 -44
  48. package/src/components/ui/spinner.tsx +32 -32
  49. package/src/components/ui/switch.tsx +31 -31
  50. package/src/components/ui/table.tsx +121 -121
  51. package/src/components/ui/tabs.tsx +81 -81
  52. package/src/components/ui/textarea.tsx +19 -19
  53. package/src/components/ui/toggle-group.tsx +89 -89
  54. package/src/components/ui/toggle.tsx +44 -44
  55. package/src/components/ui/tooltip.tsx +65 -65
  56. package/src/hooks/use-mobile.ts +20 -20
  57. package/src/lib/i18n.tsx +109 -109
  58. package/src/lib/utils.ts +1 -1
  59. package/src/patterns/app-shell.tsx +177 -175
  60. package/src/patterns/data-table.tsx +2486 -2486
  61. package/src/patterns/details-sheet.tsx +54 -53
  62. package/src/patterns/filter-tabs.tsx +53 -53
  63. package/src/patterns/majapi-logo.tsx +32 -32
  64. package/src/patterns/page-header.tsx +62 -62
  65. package/src/patterns/preferences.tsx +15 -7
  66. package/src/patterns/status-badge.tsx +24 -24
  67. package/src/patterns/user-menu.tsx +218 -0
  68. package/src/patterns/workspace-switcher.tsx +176 -0
  69. package/src/styles.css +165 -165
  70. package/src/templates/admin-dashboard.tsx +44 -44
  71. package/src/templates/auth-layout.tsx +30 -30
  72. package/src/templates/login-form.tsx +129 -129
  73. package/src/templates/signup-form.tsx +177 -177
@@ -1,53 +1,54 @@
1
- "use client";
2
-
3
- import type { ReactNode } from "react";
4
- import {
5
- Sheet,
6
- SheetContent,
7
- SheetHeader,
8
- SheetTitle,
9
- SheetDescription,
10
- } from "../components/ui/sheet";
11
- export function DetailsSheet({
12
- open,
13
- onOpenChange,
14
- title,
15
- description,
16
- children,
17
- }: {
18
- open: boolean;
19
- onOpenChange: (open: boolean) => void;
20
- title: string;
21
- description?: string;
22
- children: ReactNode;
23
- }) {
24
- return (
25
- <Sheet open={open} onOpenChange={onOpenChange}>
26
- <SheetContent className="overflow-y-auto">
27
- <SheetHeader>
28
- <SheetTitle>{title}</SheetTitle>
29
- {description ? (
30
- <SheetDescription>{description}</SheetDescription>
31
- ) : null}
32
- </SheetHeader>
33
- <div className="px-4 pb-8">{children}</div>
34
- </SheetContent>
35
- </Sheet>
36
- );
37
- }
38
- export function DetailList({
39
- items,
40
- }: {
41
- items: { label: string; value: ReactNode }[];
42
- }) {
43
- return (
44
- <dl className="grid gap-4 text-sm">
45
- {items.map((item) => (
46
- <div key={item.label} className="grid gap-1">
47
- <dt className="text-muted-foreground">{item.label}</dt>
48
- <dd className="font-medium break-words">{item.value}</dd>
49
- </div>
50
- ))}
51
- </dl>
52
- );
53
- }
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+ import {
5
+ Sheet,
6
+ SheetContent,
7
+ SheetHeader,
8
+ SheetTitle,
9
+ SheetDescription,
10
+ } from "../components/ui/sheet";
11
+ export function DetailsSheet({
12
+ open,
13
+ onOpenChange,
14
+ title,
15
+ description,
16
+ children,
17
+ }: {
18
+ open: boolean;
19
+ onOpenChange: (open: boolean) => void;
20
+ title: string;
21
+ description?: string;
22
+ children: ReactNode;
23
+ }) {
24
+ return (
25
+ <Sheet open={open} onOpenChange={onOpenChange}>
26
+ <SheetContent className="overflow-y-auto">
27
+ <SheetHeader>
28
+ <SheetTitle>{title}</SheetTitle>
29
+ {description ? (
30
+ <SheetDescription>{description}</SheetDescription>
31
+ ) : null}
32
+ </SheetHeader>
33
+ {/* Same inset as SheetHeader (p-6). */}
34
+ <div className="px-6 pb-8">{children}</div>
35
+ </SheetContent>
36
+ </Sheet>
37
+ );
38
+ }
39
+ export function DetailList({
40
+ items,
41
+ }: {
42
+ items: { label: string; value: ReactNode }[];
43
+ }) {
44
+ return (
45
+ <dl className="grid gap-4 text-sm">
46
+ {items.map((item) => (
47
+ <div key={item.label} className="grid gap-1">
48
+ <dt className="text-muted-foreground">{item.label}</dt>
49
+ <dd className="font-medium break-words">{item.value}</dd>
50
+ </div>
51
+ ))}
52
+ </dl>
53
+ );
54
+ }
@@ -1,53 +1,53 @@
1
- "use client";
2
-
3
- import type * as React from "react";
4
-
5
- import { Badge } from "../components/ui/badge";
6
- import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs";
7
- import { cn } from "cn";
8
-
9
- export type FilterTabOption = {
10
- count?: number;
11
- label: React.ReactNode;
12
- value: string;
13
- };
14
-
15
- export function FilterTabs({
16
- className,
17
- onValueChange,
18
- options,
19
- value,
20
- }: {
21
- className?: string;
22
- onValueChange: (value: string) => void;
23
- options: FilterTabOption[];
24
- value: string;
25
- }) {
26
- return (
27
- <Tabs
28
- value={value}
29
- onValueChange={onValueChange}
30
- className={cn("min-w-0 max-w-full", className)}
31
- >
32
- <TabsList className="max-w-full flex-wrap justify-start gap-y-1 group-data-horizontal/tabs:h-auto">
33
- {options.map((option) => (
34
- <TabsTrigger
35
- key={option.value}
36
- value={option.value}
37
- className="flex-none"
38
- >
39
- {option.label}
40
- {typeof option.count === "number" && (
41
- <Badge
42
- className="min-w-5 px-1.5 tabular-nums"
43
- variant="secondary"
44
- >
45
- {option.count}
46
- </Badge>
47
- )}
48
- </TabsTrigger>
49
- ))}
50
- </TabsList>
51
- </Tabs>
52
- );
53
- }
1
+ "use client";
2
+
3
+ import type * as React from "react";
4
+
5
+ import { Badge } from "../components/ui/badge";
6
+ import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs";
7
+ import { cn } from "cn";
8
+
9
+ export type FilterTabOption = {
10
+ count?: number;
11
+ label: React.ReactNode;
12
+ value: string;
13
+ };
14
+
15
+ export function FilterTabs({
16
+ className,
17
+ onValueChange,
18
+ options,
19
+ value,
20
+ }: {
21
+ className?: string;
22
+ onValueChange: (value: string) => void;
23
+ options: FilterTabOption[];
24
+ value: string;
25
+ }) {
26
+ return (
27
+ <Tabs
28
+ value={value}
29
+ onValueChange={onValueChange}
30
+ className={cn("min-w-0 max-w-full", className)}
31
+ >
32
+ <TabsList className="max-w-full flex-wrap justify-start gap-y-1 group-data-horizontal/tabs:h-auto">
33
+ {options.map((option) => (
34
+ <TabsTrigger
35
+ key={option.value}
36
+ value={option.value}
37
+ className="flex-none"
38
+ >
39
+ {option.label}
40
+ {typeof option.count === "number" && (
41
+ <Badge
42
+ className="min-w-5 px-1.5 tabular-nums"
43
+ variant="secondary"
44
+ >
45
+ {option.count}
46
+ </Badge>
47
+ )}
48
+ </TabsTrigger>
49
+ ))}
50
+ </TabsList>
51
+ </Tabs>
52
+ );
53
+ }
@@ -1,32 +1,32 @@
1
- import type { SVGProps } from "react";
2
- import { cn } from "cn";
3
-
4
- const MARK =
5
- "M25.9083 4.2714C25.7631 4.43736 25.6179 4.60332 25.4727 4.76929C25.3274 4.9145 25.2133 5.07009 25.1304 5.23605C24.5495 6.19033 23.8442 7.22758 23.0144 8.34782C22.2053 9.46805 21.2821 10.6194 20.2449 11.8019C19.2076 12.9843 18.077 14.1461 16.8531 15.2871C15.6499 16.428 14.3533 17.4964 12.9634 18.4922L11.0341 16.7807C12.2995 15.9094 13.482 14.9655 14.5815 13.949C15.681 12.9325 16.6871 11.9056 17.5999 10.8683C18.5334 9.81035 19.3425 8.80421 20.0271 7.84993C20.7324 6.89566 21.2821 6.04511 21.6763 5.29829C21.2821 5.29829 20.6909 5.29829 19.9026 5.29829C19.1143 5.29829 18.2015 5.29829 17.1642 5.29829C16.127 5.29829 15.0275 5.29829 13.8658 5.29829C12.7248 5.29829 11.5838 5.29829 10.4428 5.29829C9.32259 5.29829 8.27497 5.29829 7.29995 5.29829C6.32493 5.29829 5.48475 5.29829 4.77941 5.29829C4.09483 5.29829 3.62806 5.29829 3.37912 5.29829C2.98496 5.29829 2.58043 5.30866 2.16553 5.3294C1.75063 5.35015 1.36685 5.37089 1.01418 5.39164C0.682256 5.41238 0.41257 5.4435 0.205119 5.48499V2.6844C0.495551 2.72589 0.8171 2.76738 1.16977 2.80887C1.52243 2.85036 1.88547 2.88148 2.25888 2.90223C2.65304 2.92297 3.02645 2.93334 3.37912 2.93334C3.60732 2.93334 4.05334 2.93334 4.71718 2.93334C5.40177 2.93334 6.23157 2.93334 7.20659 2.93334C8.18161 2.93334 9.22924 2.93334 10.3495 2.93334C11.4905 2.93334 12.6418 2.93334 13.8035 2.93334C14.986 2.93334 16.1062 2.93334 17.1642 2.93334C18.243 2.93334 19.1973 2.93334 20.0271 2.93334C20.8569 2.93334 21.4896 2.93334 21.9252 2.93334C22.8173 2.93334 23.4915 2.85036 23.9479 2.6844L25.9083 4.2714ZM11.2519 19.0523C10.7748 18.5752 10.2146 18.0462 9.57153 17.4653C8.94918 16.8844 8.29571 16.3036 7.61112 15.7227C6.92653 15.1211 6.28344 14.5817 5.68183 14.1046C5.08022 13.6067 4.56159 13.2125 4.12594 12.9221L6.14859 11.2729C6.50126 11.5426 6.96802 11.916 7.54889 12.3931C8.12975 12.8495 8.76248 13.3681 9.44706 13.949C10.1524 14.5299 10.8474 15.1211 11.5319 15.7227C12.2165 16.3243 12.8389 16.9052 13.399 17.4653C14.0628 18.0876 14.7474 18.7722 15.4528 19.5191C16.1789 20.2659 16.8634 20.992 17.5065 21.6973C18.1496 22.4026 18.689 23.0354 19.1247 23.5955L16.8531 25.4003C16.4797 24.8609 15.9714 24.2282 15.3283 23.5021C14.7059 22.7553 14.0317 21.9981 13.3057 21.2305C12.5796 20.4422 11.895 19.7161 11.2519 19.0523Z";
6
- const TEXT =
7
- "M55.2228 24.3057H52.6089L51.9555 13.1033C51.831 11.1325 51.7688 8.54976 51.7688 5.35501L48.1591 20.4471H45.4519L41.5933 5.35501C41.6348 6.10183 41.6555 7.0976 41.6555 8.3423C41.6555 10.2509 41.614 11.8379 41.5311 13.1033L40.9709 24.3057H38.4193L39.6329 2.8656H43.3981L46.8833 17.5531L50.2129 2.8656H54.0092L55.2228 24.3057ZM71.0184 20.7271C71.0184 21.3702 71.1222 21.8474 71.3296 22.1585C71.5371 22.449 71.8794 22.6668 72.3565 22.812L71.703 24.6791C70.1057 24.4716 69.1099 23.6937 68.7157 22.3453C68.1349 23.0921 67.3984 23.6626 66.5064 24.0567C65.6351 24.4509 64.6601 24.648 63.5813 24.648C61.9425 24.648 60.6459 24.1916 59.6916 23.2788C58.7581 22.366 58.2913 21.142 58.2913 19.6069C58.2913 17.9265 58.9448 16.63 60.2517 15.7172C61.5794 14.8044 63.4776 14.348 65.9463 14.348H68.3734V13.01C68.3734 11.8482 68.0415 11.0184 67.3777 10.5205C66.7138 10.0019 65.7492 9.7426 64.4837 9.7426C63.239 9.7426 61.818 10.0227 60.2206 10.5828L59.5049 8.59125C61.4342 7.90666 63.2287 7.56436 64.8883 7.56436C66.8798 7.56436 68.3942 8.03113 69.4314 8.96466C70.4894 9.89819 71.0184 11.1948 71.0184 12.8544V20.7271ZM64.2348 22.6875C65.0439 22.6875 65.8114 22.4801 66.5375 22.0652C67.2843 21.6503 67.8963 21.0798 68.3734 20.3537V16.1528H66.0085C64.2659 16.1528 63.0108 16.4433 62.2433 17.0241C61.4757 17.605 61.0919 18.4555 61.0919 19.5758C61.0919 21.6503 62.1395 22.6875 64.2348 22.6875ZM86.2228 0.251712C86.7829 0.251712 87.2393 0.428046 87.592 0.780713C87.9447 1.11264 88.121 1.54828 88.121 2.08765C88.121 2.62703 87.9447 3.07305 87.592 3.42572C87.2393 3.75764 86.7829 3.9236 86.2228 3.9236C85.6627 3.9236 85.2063 3.75764 84.8536 3.42572C84.501 3.07305 84.3246 2.62703 84.3246 2.08765C84.3246 1.54828 84.501 1.11264 84.8536 0.780713C85.2063 0.428046 85.6627 0.251712 86.2228 0.251712ZM88.65 21.6918C88.65 24.3264 87.7683 26.3698 86.005 27.822C84.2624 29.2949 81.6693 30.3217 78.2256 30.9026L77.7899 28.88C80.4246 28.4443 82.4576 27.6767 83.889 26.5773C85.3204 25.4985 86.0361 23.9219 86.0361 21.8474V9.99154H79.2214V7.90666H88.65V21.6918ZM108.335 20.7271C108.335 21.3702 108.439 21.8474 108.647 22.1585C108.854 22.449 109.196 22.6668 109.673 22.812L109.02 24.6791C107.423 24.4716 106.427 23.6937 106.033 22.3453C105.452 23.0921 104.715 23.6626 103.823 24.0567C102.952 24.4509 101.977 24.648 100.898 24.648C99.2593 24.648 97.9628 24.1916 97.0085 23.2788C96.075 22.366 95.6082 21.142 95.6082 19.6069C95.6082 17.9265 96.2617 16.63 97.5686 15.7172C98.8963 14.8044 100.794 14.348 103.263 14.348H105.69V13.01C105.69 11.8482 105.358 11.0184 104.695 10.5205C104.031 10.0019 103.066 9.7426 101.801 9.7426C100.556 9.7426 99.1349 10.0227 97.5375 10.5828L96.8218 8.59125C98.7511 7.90666 100.546 7.56436 102.205 7.56436C104.197 7.56436 105.711 8.03113 106.748 8.96466C107.806 9.89819 108.335 11.1948 108.335 12.8544V20.7271ZM101.552 22.6875C102.361 22.6875 103.128 22.4801 103.854 22.0652C104.601 21.6503 105.213 21.0798 105.69 20.3537V16.1528H103.325C101.583 16.1528 100.328 16.4433 99.5601 17.0241C98.7926 17.605 98.4088 18.4555 98.4088 19.5758C98.4088 21.6503 99.4564 22.6875 101.552 22.6875ZM122.575 7.56436C126.537 7.56436 128.519 10.4064 128.519 16.0906C128.519 18.6837 127.979 20.7583 126.9 22.3141C125.822 23.87 124.255 24.648 122.202 24.648C120.418 24.648 118.965 24.0256 117.845 22.7809V30.5914L115.231 30.9026V7.90666H117.472L117.69 10.2094C118.291 9.35881 119.007 8.70534 119.837 8.24895C120.687 7.79256 121.6 7.56436 122.575 7.56436ZM121.953 9.68036C121.123 9.68036 120.355 9.92931 119.65 10.4272C118.965 10.9251 118.364 11.5474 117.845 12.2942V20.416C118.281 21.0591 118.82 21.5673 119.463 21.9407C120.127 22.2934 120.843 22.4697 121.61 22.4697C122.98 22.4697 123.996 21.9511 124.66 20.9138C125.345 19.8558 125.687 18.2481 125.687 16.0906C125.687 13.9331 125.386 12.3254 124.784 11.2674C124.183 10.2094 123.239 9.68036 121.953 9.68036ZM140.113 0.251712C140.694 0.251712 141.161 0.428046 141.514 0.780713C141.866 1.11264 142.043 1.54828 142.043 2.08765C142.043 2.62703 141.866 3.07305 141.514 3.42572C141.161 3.75764 140.694 3.9236 140.113 3.9236C139.553 3.9236 139.097 3.75764 138.744 3.42572C138.412 3.07305 138.246 2.62703 138.246 2.08765C138.246 1.54828 138.412 1.11264 138.744 0.780713C139.097 0.428046 139.553 0.251712 140.113 0.251712ZM142.198 22.2208H146.804V24.3057H134.543V22.2208H139.584V9.99154H134.699V7.90666H142.198V22.2208Z";
8
-
9
- /**
10
- * Majapi logo in the current text color; `mark` is the マ symbol alone.
11
- * Size it by height only (e.g. `h-6`); the width follows the aspect ratio.
12
- */
13
- export function MajapiLogo({
14
- variant = "full",
15
- className,
16
- ...props
17
- }: SVGProps<SVGSVGElement> & { variant?: "full" | "mark" }) {
18
- return (
19
- <svg
20
- viewBox={variant === "mark" ? "0 0 27 31" : "0 0 147 31"}
21
- fill="currentColor"
22
- role="img"
23
- aria-label="Majapi"
24
- preserveAspectRatio="xMinYMid meet"
25
- className={cn("h-5 w-auto shrink-0", className)}
26
- {...props}
27
- >
28
- <path d={MARK} />
29
- {variant === "full" ? <path d={TEXT} /> : null}
30
- </svg>
31
- );
32
- }
1
+ import type { SVGProps } from "react";
2
+ import { cn } from "cn";
3
+
4
+ const MARK =
5
+ "M25.9083 4.2714C25.7631 4.43736 25.6179 4.60332 25.4727 4.76929C25.3274 4.9145 25.2133 5.07009 25.1304 5.23605C24.5495 6.19033 23.8442 7.22758 23.0144 8.34782C22.2053 9.46805 21.2821 10.6194 20.2449 11.8019C19.2076 12.9843 18.077 14.1461 16.8531 15.2871C15.6499 16.428 14.3533 17.4964 12.9634 18.4922L11.0341 16.7807C12.2995 15.9094 13.482 14.9655 14.5815 13.949C15.681 12.9325 16.6871 11.9056 17.5999 10.8683C18.5334 9.81035 19.3425 8.80421 20.0271 7.84993C20.7324 6.89566 21.2821 6.04511 21.6763 5.29829C21.2821 5.29829 20.6909 5.29829 19.9026 5.29829C19.1143 5.29829 18.2015 5.29829 17.1642 5.29829C16.127 5.29829 15.0275 5.29829 13.8658 5.29829C12.7248 5.29829 11.5838 5.29829 10.4428 5.29829C9.32259 5.29829 8.27497 5.29829 7.29995 5.29829C6.32493 5.29829 5.48475 5.29829 4.77941 5.29829C4.09483 5.29829 3.62806 5.29829 3.37912 5.29829C2.98496 5.29829 2.58043 5.30866 2.16553 5.3294C1.75063 5.35015 1.36685 5.37089 1.01418 5.39164C0.682256 5.41238 0.41257 5.4435 0.205119 5.48499V2.6844C0.495551 2.72589 0.8171 2.76738 1.16977 2.80887C1.52243 2.85036 1.88547 2.88148 2.25888 2.90223C2.65304 2.92297 3.02645 2.93334 3.37912 2.93334C3.60732 2.93334 4.05334 2.93334 4.71718 2.93334C5.40177 2.93334 6.23157 2.93334 7.20659 2.93334C8.18161 2.93334 9.22924 2.93334 10.3495 2.93334C11.4905 2.93334 12.6418 2.93334 13.8035 2.93334C14.986 2.93334 16.1062 2.93334 17.1642 2.93334C18.243 2.93334 19.1973 2.93334 20.0271 2.93334C20.8569 2.93334 21.4896 2.93334 21.9252 2.93334C22.8173 2.93334 23.4915 2.85036 23.9479 2.6844L25.9083 4.2714ZM11.2519 19.0523C10.7748 18.5752 10.2146 18.0462 9.57153 17.4653C8.94918 16.8844 8.29571 16.3036 7.61112 15.7227C6.92653 15.1211 6.28344 14.5817 5.68183 14.1046C5.08022 13.6067 4.56159 13.2125 4.12594 12.9221L6.14859 11.2729C6.50126 11.5426 6.96802 11.916 7.54889 12.3931C8.12975 12.8495 8.76248 13.3681 9.44706 13.949C10.1524 14.5299 10.8474 15.1211 11.5319 15.7227C12.2165 16.3243 12.8389 16.9052 13.399 17.4653C14.0628 18.0876 14.7474 18.7722 15.4528 19.5191C16.1789 20.2659 16.8634 20.992 17.5065 21.6973C18.1496 22.4026 18.689 23.0354 19.1247 23.5955L16.8531 25.4003C16.4797 24.8609 15.9714 24.2282 15.3283 23.5021C14.7059 22.7553 14.0317 21.9981 13.3057 21.2305C12.5796 20.4422 11.895 19.7161 11.2519 19.0523Z";
6
+ const TEXT =
7
+ "M55.2228 24.3057H52.6089L51.9555 13.1033C51.831 11.1325 51.7688 8.54976 51.7688 5.35501L48.1591 20.4471H45.4519L41.5933 5.35501C41.6348 6.10183 41.6555 7.0976 41.6555 8.3423C41.6555 10.2509 41.614 11.8379 41.5311 13.1033L40.9709 24.3057H38.4193L39.6329 2.8656H43.3981L46.8833 17.5531L50.2129 2.8656H54.0092L55.2228 24.3057ZM71.0184 20.7271C71.0184 21.3702 71.1222 21.8474 71.3296 22.1585C71.5371 22.449 71.8794 22.6668 72.3565 22.812L71.703 24.6791C70.1057 24.4716 69.1099 23.6937 68.7157 22.3453C68.1349 23.0921 67.3984 23.6626 66.5064 24.0567C65.6351 24.4509 64.6601 24.648 63.5813 24.648C61.9425 24.648 60.6459 24.1916 59.6916 23.2788C58.7581 22.366 58.2913 21.142 58.2913 19.6069C58.2913 17.9265 58.9448 16.63 60.2517 15.7172C61.5794 14.8044 63.4776 14.348 65.9463 14.348H68.3734V13.01C68.3734 11.8482 68.0415 11.0184 67.3777 10.5205C66.7138 10.0019 65.7492 9.7426 64.4837 9.7426C63.239 9.7426 61.818 10.0227 60.2206 10.5828L59.5049 8.59125C61.4342 7.90666 63.2287 7.56436 64.8883 7.56436C66.8798 7.56436 68.3942 8.03113 69.4314 8.96466C70.4894 9.89819 71.0184 11.1948 71.0184 12.8544V20.7271ZM64.2348 22.6875C65.0439 22.6875 65.8114 22.4801 66.5375 22.0652C67.2843 21.6503 67.8963 21.0798 68.3734 20.3537V16.1528H66.0085C64.2659 16.1528 63.0108 16.4433 62.2433 17.0241C61.4757 17.605 61.0919 18.4555 61.0919 19.5758C61.0919 21.6503 62.1395 22.6875 64.2348 22.6875ZM86.2228 0.251712C86.7829 0.251712 87.2393 0.428046 87.592 0.780713C87.9447 1.11264 88.121 1.54828 88.121 2.08765C88.121 2.62703 87.9447 3.07305 87.592 3.42572C87.2393 3.75764 86.7829 3.9236 86.2228 3.9236C85.6627 3.9236 85.2063 3.75764 84.8536 3.42572C84.501 3.07305 84.3246 2.62703 84.3246 2.08765C84.3246 1.54828 84.501 1.11264 84.8536 0.780713C85.2063 0.428046 85.6627 0.251712 86.2228 0.251712ZM88.65 21.6918C88.65 24.3264 87.7683 26.3698 86.005 27.822C84.2624 29.2949 81.6693 30.3217 78.2256 30.9026L77.7899 28.88C80.4246 28.4443 82.4576 27.6767 83.889 26.5773C85.3204 25.4985 86.0361 23.9219 86.0361 21.8474V9.99154H79.2214V7.90666H88.65V21.6918ZM108.335 20.7271C108.335 21.3702 108.439 21.8474 108.647 22.1585C108.854 22.449 109.196 22.6668 109.673 22.812L109.02 24.6791C107.423 24.4716 106.427 23.6937 106.033 22.3453C105.452 23.0921 104.715 23.6626 103.823 24.0567C102.952 24.4509 101.977 24.648 100.898 24.648C99.2593 24.648 97.9628 24.1916 97.0085 23.2788C96.075 22.366 95.6082 21.142 95.6082 19.6069C95.6082 17.9265 96.2617 16.63 97.5686 15.7172C98.8963 14.8044 100.794 14.348 103.263 14.348H105.69V13.01C105.69 11.8482 105.358 11.0184 104.695 10.5205C104.031 10.0019 103.066 9.7426 101.801 9.7426C100.556 9.7426 99.1349 10.0227 97.5375 10.5828L96.8218 8.59125C98.7511 7.90666 100.546 7.56436 102.205 7.56436C104.197 7.56436 105.711 8.03113 106.748 8.96466C107.806 9.89819 108.335 11.1948 108.335 12.8544V20.7271ZM101.552 22.6875C102.361 22.6875 103.128 22.4801 103.854 22.0652C104.601 21.6503 105.213 21.0798 105.69 20.3537V16.1528H103.325C101.583 16.1528 100.328 16.4433 99.5601 17.0241C98.7926 17.605 98.4088 18.4555 98.4088 19.5758C98.4088 21.6503 99.4564 22.6875 101.552 22.6875ZM122.575 7.56436C126.537 7.56436 128.519 10.4064 128.519 16.0906C128.519 18.6837 127.979 20.7583 126.9 22.3141C125.822 23.87 124.255 24.648 122.202 24.648C120.418 24.648 118.965 24.0256 117.845 22.7809V30.5914L115.231 30.9026V7.90666H117.472L117.69 10.2094C118.291 9.35881 119.007 8.70534 119.837 8.24895C120.687 7.79256 121.6 7.56436 122.575 7.56436ZM121.953 9.68036C121.123 9.68036 120.355 9.92931 119.65 10.4272C118.965 10.9251 118.364 11.5474 117.845 12.2942V20.416C118.281 21.0591 118.82 21.5673 119.463 21.9407C120.127 22.2934 120.843 22.4697 121.61 22.4697C122.98 22.4697 123.996 21.9511 124.66 20.9138C125.345 19.8558 125.687 18.2481 125.687 16.0906C125.687 13.9331 125.386 12.3254 124.784 11.2674C124.183 10.2094 123.239 9.68036 121.953 9.68036ZM140.113 0.251712C140.694 0.251712 141.161 0.428046 141.514 0.780713C141.866 1.11264 142.043 1.54828 142.043 2.08765C142.043 2.62703 141.866 3.07305 141.514 3.42572C141.161 3.75764 140.694 3.9236 140.113 3.9236C139.553 3.9236 139.097 3.75764 138.744 3.42572C138.412 3.07305 138.246 2.62703 138.246 2.08765C138.246 1.54828 138.412 1.11264 138.744 0.780713C139.097 0.428046 139.553 0.251712 140.113 0.251712ZM142.198 22.2208H146.804V24.3057H134.543V22.2208H139.584V9.99154H134.699V7.90666H142.198V22.2208Z";
8
+
9
+ /**
10
+ * Majapi logo in the current text color; `mark` is the マ symbol alone.
11
+ * Size it by height only (e.g. `h-6`); the width follows the aspect ratio.
12
+ */
13
+ export function MajapiLogo({
14
+ variant = "full",
15
+ className,
16
+ ...props
17
+ }: SVGProps<SVGSVGElement> & { variant?: "full" | "mark" }) {
18
+ return (
19
+ <svg
20
+ viewBox={variant === "mark" ? "0 0 27 31" : "0 0 147 31"}
21
+ fill="currentColor"
22
+ role="img"
23
+ aria-label="Majapi"
24
+ preserveAspectRatio="xMinYMid meet"
25
+ className={cn("h-5 w-auto shrink-0", className)}
26
+ {...props}
27
+ >
28
+ <path d={MARK} />
29
+ {variant === "full" ? <path d={TEXT} /> : null}
30
+ </svg>
31
+ );
32
+ }
@@ -1,62 +1,62 @@
1
- import type { ReactNode } from "react";
2
- import {
3
- Card,
4
- CardAction,
5
- CardDescription,
6
- CardFooter,
7
- CardHeader,
8
- CardTitle,
9
- } from "../components/ui/card";
10
-
11
- export function PageHeader({
12
- title,
13
- description,
14
- actions,
15
- }: {
16
- title: string;
17
- description?: string;
18
- actions?: ReactNode;
19
- }) {
20
- return (
21
- <div className="flex flex-wrap items-end justify-between gap-4">
22
- <div className="flex flex-col gap-1">
23
- <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
24
- {description ? (
25
- <p className="text-sm text-muted-foreground">{description}</p>
26
- ) : null}
27
- </div>
28
- {actions ? (
29
- <div className="flex items-center gap-2">{actions}</div>
30
- ) : null}
31
- </div>
32
- );
33
- }
34
-
35
- export interface Metric {
36
- label: string;
37
- value: ReactNode;
38
- badge?: ReactNode;
39
- note?: ReactNode;
40
- }
41
- export function MetricStrip({ items }: { items: Metric[] }) {
42
- return (
43
- <div className="grid gap-4 sm:grid-cols-2 md:gap-6 xl:grid-cols-4">
44
- {items.map((item) => (
45
- <Card key={item.label}>
46
- <CardHeader>
47
- <CardDescription>{item.label}</CardDescription>
48
- <CardTitle className="text-2xl font-semibold tabular-nums">
49
- {item.value}
50
- </CardTitle>
51
- {item.badge ? <CardAction>{item.badge}</CardAction> : null}
52
- </CardHeader>
53
- {item.note ? (
54
- <CardFooter className="text-sm text-muted-foreground">
55
- {item.note}
56
- </CardFooter>
57
- ) : null}
58
- </Card>
59
- ))}
60
- </div>
61
- );
62
- }
1
+ import type { ReactNode } from "react";
2
+ import {
3
+ Card,
4
+ CardAction,
5
+ CardDescription,
6
+ CardFooter,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from "../components/ui/card";
10
+
11
+ export function PageHeader({
12
+ title,
13
+ description,
14
+ actions,
15
+ }: {
16
+ title: string;
17
+ description?: string;
18
+ actions?: ReactNode;
19
+ }) {
20
+ return (
21
+ <div className="flex flex-wrap items-end justify-between gap-4">
22
+ <div className="flex flex-col gap-1">
23
+ <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
24
+ {description ? (
25
+ <p className="text-sm text-muted-foreground">{description}</p>
26
+ ) : null}
27
+ </div>
28
+ {actions ? (
29
+ <div className="flex items-center gap-2">{actions}</div>
30
+ ) : null}
31
+ </div>
32
+ );
33
+ }
34
+
35
+ export interface Metric {
36
+ label: string;
37
+ value: ReactNode;
38
+ badge?: ReactNode;
39
+ note?: ReactNode;
40
+ }
41
+ export function MetricStrip({ items }: { items: Metric[] }) {
42
+ return (
43
+ <div className="grid gap-4 sm:grid-cols-2 md:gap-6 xl:grid-cols-4">
44
+ {items.map((item) => (
45
+ <Card key={item.label}>
46
+ <CardHeader>
47
+ <CardDescription>{item.label}</CardDescription>
48
+ <CardTitle className="text-2xl font-semibold tabular-nums">
49
+ {item.value}
50
+ </CardTitle>
51
+ {item.badge ? <CardAction>{item.badge}</CardAction> : null}
52
+ </CardHeader>
53
+ {item.note ? (
54
+ <CardFooter className="text-sm text-muted-foreground">
55
+ {item.note}
56
+ </CardFooter>
57
+ ) : null}
58
+ </Card>
59
+ ))}
60
+ </div>
61
+ );
62
+ }
@@ -32,6 +32,7 @@ const PreferencesContext = createContext<{
32
32
  theme: Theme;
33
33
  setTheme: (theme: Theme) => void;
34
34
  } | null>(null);
35
+ const PreferencesReadyContext = createContext(true);
35
36
 
36
37
  function read(key: string) {
37
38
  try {
@@ -53,7 +54,7 @@ function write(key: string, value: string) {
53
54
  * Inline this in `<head>` (e.g. `<script dangerouslySetInnerHTML>`) so the
54
55
  * stored theme applies before paint.
55
56
  */
56
- export const preferencesScript = `try{var d=document.documentElement;if(localStorage.getItem("theme")==="dark")d.classList.add("dark");var l=localStorage.getItem("locale");if(l!=="en"&&l!=="pt-BR"){l="en";var a=navigator.languages||[];for(var i=0;i<a.length;i++){if(/^pt/i.test(a[i])){l="pt-BR";break}if(/^en/i.test(a[i]))break}}if(l!=="en"){d.lang=l;d.setAttribute("data-locale-pending",l);setTimeout(function(){d.removeAttribute("data-locale-pending")},2000)}}catch(e){}`;
57
+ export const preferencesScript = `try{var d=document.documentElement;if(localStorage.getItem("theme")==="dark")d.classList.add("dark");var l=localStorage.getItem("locale");if(l!=="en"&&l!=="pt-BR"){l="en";var a=navigator.languages||[];for(var i=0;i<a.length;i++){if(/^pt/i.test(a[i])){l="pt-BR";break}if(/^en/i.test(a[i]))break}}if(l!==d.lang){d.lang=l;d.setAttribute("data-locale-pending",l)}}catch(e){}`;
57
58
 
58
59
  /**
59
60
  * Locale + theme for the whole app. Renders `defaultLocale` first (matches
@@ -68,6 +69,7 @@ export function Preferences({
68
69
  }) {
69
70
  const [locale, setLocale] = useState<Locale>(defaultLocale);
70
71
  const [theme, setTheme] = useState<Theme>("light");
72
+ const [ready, setReady] = useState(false);
71
73
  useEffect(() => {
72
74
  const stored = read("locale");
73
75
  // eslint-disable-next-line react-hooks/set-state-in-effect -- sync from browser-only storage after hydration
@@ -77,14 +79,16 @@ export function Preferences({
77
79
  : matchLocale(navigator.languages),
78
80
  );
79
81
  setTheme(read("theme") === "dark" ? "dark" : "light");
82
+ setReady(true);
80
83
  }, []);
81
84
  useEffect(() => {
85
+ if (!ready) return;
82
86
  const root = document.documentElement;
83
87
  root.lang = locale;
84
88
  // The pre-paint script hides the page until the saved locale is rendered.
85
89
  if (root.dataset.localePending === locale)
86
90
  delete root.dataset.localePending;
87
- }, [locale]);
91
+ }, [locale, ready]);
88
92
  useEffect(() => {
89
93
  document.documentElement.classList.toggle("dark", theme === "dark");
90
94
  }, [theme]);
@@ -103,10 +107,12 @@ export function Preferences({
103
107
  },
104
108
  }}
105
109
  >
106
- <LocaleProvider locale={locale}>
107
- {children}
108
- <Toaster theme={theme} />
109
- </LocaleProvider>
110
+ <PreferencesReadyContext.Provider value={ready}>
111
+ <LocaleProvider locale={locale}>
112
+ {children}
113
+ <Toaster theme={theme} />
114
+ </LocaleProvider>
115
+ </PreferencesReadyContext.Provider>
110
116
  </PreferencesContext.Provider>
111
117
  );
112
118
  }
@@ -116,9 +122,11 @@ export function Preferences({
116
122
  * of Next.js metadata, or Next will overwrite it after hydration.
117
123
  */
118
124
  export function useDocumentTitle(title: string) {
125
+ const ready = useContext(PreferencesReadyContext);
119
126
  useEffect(() => {
127
+ if (!ready) return;
120
128
  document.title = title;
121
- }, [title]);
129
+ }, [title, ready]);
122
130
  }
123
131
 
124
132
  export function usePreferences() {
@@ -1,24 +1,24 @@
1
- import type { ReactNode } from "react";
2
- import { Badge } from "../components/ui/badge";
3
- export function StatusBadge({
4
- children,
5
- tone = "neutral",
6
- }: {
7
- children: ReactNode;
8
- tone?: "neutral" | "success" | "warning" | "danger";
9
- }) {
10
- return (
11
- <Badge
12
- variant={
13
- tone === "danger"
14
- ? "destructive"
15
- : tone === "neutral"
16
- ? "outline"
17
- : tone
18
- }
19
- >
20
- <span className="size-1.5 rounded-full bg-current" aria-hidden="true" />
21
- {children}
22
- </Badge>
23
- );
24
- }
1
+ import type { ReactNode } from "react";
2
+ import { Badge } from "../components/ui/badge";
3
+ export function StatusBadge({
4
+ children,
5
+ tone = "neutral",
6
+ }: {
7
+ children: ReactNode;
8
+ tone?: "neutral" | "success" | "warning" | "danger";
9
+ }) {
10
+ return (
11
+ <Badge
12
+ variant={
13
+ tone === "danger"
14
+ ? "destructive"
15
+ : tone === "neutral"
16
+ ? "outline"
17
+ : tone
18
+ }
19
+ >
20
+ <span className="size-1.5 rounded-full bg-current" aria-hidden="true" />
21
+ {children}
22
+ </Badge>
23
+ );
24
+ }