@lukoweb/apitogo 0.1.87 → 0.1.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/cli.js CHANGED
@@ -815,7 +815,8 @@ var init_ModulesSchema = __esm({
815
815
  path: z3.string(),
816
816
  icon: z3.string().optional(),
817
817
  latency: z3.string().optional(),
818
- summary: z3.string().optional()
818
+ summary: z3.string().optional(),
819
+ method: z3.string().optional()
819
820
  });
820
821
  LandingApiShowcaseSchema = z3.object({
821
822
  enabled: z3.boolean().optional(),
@@ -824,6 +825,7 @@ var init_ModulesSchema = __esm({
824
825
  browseAllHref: z3.string().optional(),
825
826
  browseAllLabel: z3.string().optional(),
826
827
  autoFromOpenApi: z3.boolean().optional(),
828
+ layout: z3.enum(["grid", "list"]).optional(),
827
829
  items: z3.array(LandingApiShowcaseItemSchema).optional()
828
830
  }).optional();
829
831
  LandingPricingCtaSchema = z3.object({
@@ -6257,7 +6259,7 @@ var SCAFFOLD_SUGGESTED_NEXT_STEPS = [
6257
6259
  prompt: "Design your home page?",
6258
6260
  tool: "get_dev_portal_landing_guide",
6259
6261
  optional: true,
6260
- note: "Ask the user for their real content (product, endpoints, audience, tone, sections) before editing landing/LandingPage.tsx \u2014 compose HeroSection, TrustSection, FeaturesSection, ApiShowcaseSection, and PricingCtaSection inside LandingShell."
6262
+ note: "Ask the user for their real content (product, endpoints, audience, tone, sections) before editing landing/LandingPage.tsx \u2014 compose HeroSection, TrustSection, FeaturesSection, StepsSection, ApiShowcaseSection, PlansPreviewSection, and PricingCtaSection inside LandingShell."
6261
6263
  },
6262
6264
  {
6263
6265
  id: "addPlans",
@@ -12491,7 +12493,7 @@ import {
12491
12493
  // package.json
12492
12494
  var package_default = {
12493
12495
  name: "@lukoweb/apitogo",
12494
- version: "0.1.87",
12496
+ version: "0.1.88",
12495
12497
  type: "module",
12496
12498
  sideEffects: [
12497
12499
  "**/*.css",
@@ -18428,8 +18430,7 @@ async function resolveCreateApitogoInvocation(options) {
18428
18430
  return {
18429
18431
  source: "npm",
18430
18432
  command: "npx",
18431
- args: [pkg],
18432
- warning: `Using ${pkg} via npx (downloads on first run if not cached). Set APITOGO_DOC_TOOL_PATH to scaffold from a local apitogo-doc-tool checkout.`
18433
+ args: [pkg]
18433
18434
  };
18434
18435
  }
18435
18436
  function runCommand2(command, args, cwd) {
@@ -19000,44 +19001,52 @@ function createDefaultApitogoJsonConfig(options) {
19000
19001
  },
19001
19002
  theme: {
19002
19003
  fonts: {
19003
- sans: "IBM Plex Sans",
19004
+ sans: "Inter",
19004
19005
  mono: "JetBrains Mono"
19005
19006
  },
19006
19007
  light: {
19007
- background: "#F5F6F9",
19008
- foreground: "#0E1016",
19009
- card: "#FFFFFF",
19010
- cardForeground: "#0E1016",
19011
- primary: "#E11D2A",
19008
+ background: "#ffffff",
19009
+ foreground: "#252b3b",
19010
+ card: "#ffffff",
19011
+ cardForeground: "#252b3b",
19012
+ primary: "#0d9488",
19012
19013
  primaryForeground: "#ffffff",
19013
- secondary: "#FAFBFC",
19014
- secondaryForeground: "#0E1016",
19015
- muted: "#FAFBFC",
19016
- mutedForeground: "#565E70",
19017
- accent: "rgba(225, 29, 42, 0.08)",
19018
- accentForeground: "#E11D2A",
19019
- border: "#E6E8EF",
19020
- input: "#D6DAE3",
19021
- ring: "#E11D2A"
19014
+ secondary: "#f4f6fb",
19015
+ secondaryForeground: "#252b3b",
19016
+ muted: "#e9edf5",
19017
+ mutedForeground: "rgba(37, 43, 59, 0.55)",
19018
+ accent: "rgba(13, 148, 136, 0.1)",
19019
+ accentForeground: "#0d9488",
19020
+ border: "#d9deea",
19021
+ input: "#d9deea",
19022
+ ring: "#0d9488"
19022
19023
  },
19023
19024
  dark: {
19024
- background: "#0B0D12",
19025
- foreground: "#EDEFF5",
19026
- card: "#14171F",
19027
- cardForeground: "#EDEFF5",
19028
- primary: "#F23645",
19025
+ background: "#252b3b",
19026
+ foreground: "#e9edf5",
19027
+ card: "#2b3243",
19028
+ cardForeground: "#e9edf5",
19029
+ primary: "#0d9488",
19029
19030
  primaryForeground: "#ffffff",
19030
- secondary: "#191D27",
19031
- secondaryForeground: "#EDEFF5",
19032
- muted: "#191D27",
19033
- mutedForeground: "#A6AEBF",
19034
- accent: "rgba(242, 54, 69, 0.13)",
19035
- accentForeground: "#F23645",
19036
- border: "#252B37",
19037
- input: "#333A48",
19038
- ring: "#F23645"
19031
+ secondary: "#1e2330",
19032
+ secondaryForeground: "#e9edf5",
19033
+ muted: "#1e2330",
19034
+ mutedForeground: "rgba(217, 222, 234, 0.58)",
19035
+ accent: "rgba(13, 148, 136, 0.12)",
19036
+ accentForeground: "#0d9488",
19037
+ border: "rgba(217, 222, 234, 0.18)",
19038
+ input: "#323a4f",
19039
+ ring: "#0d9488"
19039
19040
  }
19040
19041
  },
19042
+ header: {
19043
+ placements: { navigation: "center" },
19044
+ navigation: [
19045
+ { label: "Product", to: "/#features" },
19046
+ { label: "Pricing", to: "/pricing" },
19047
+ { label: "Docs", to: "/introduction" }
19048
+ ]
19049
+ },
19041
19050
  modules: {
19042
19051
  docs: {
19043
19052
  files: filesGlob
@@ -19046,15 +19055,80 @@ function createDefaultApitogoJsonConfig(options) {
19046
19055
  enabled: true,
19047
19056
  path: "/",
19048
19057
  layout: "landing",
19049
- page: "./landing/LandingPage.tsx"
19058
+ page: "./landing/LandingPage.tsx",
19059
+ content: {
19060
+ footer: {
19061
+ brand: {
19062
+ name: "SampleAPI",
19063
+ tagline: "A hosted sandbox API for prototypes, demos, and QA \u2014 realistic data, real auth, real limits."
19064
+ },
19065
+ columns: [
19066
+ {
19067
+ title: "Product",
19068
+ links: [
19069
+ { label: "Overview", href: "/#features" },
19070
+ { label: "Pricing", href: "/pricing" },
19071
+ { label: "Documentation", href: "/introduction" }
19072
+ ]
19073
+ },
19074
+ {
19075
+ title: "Account",
19076
+ links: [
19077
+ { label: "Log in", href: "/signin" },
19078
+ { label: "Get started", href: "/signup" },
19079
+ { label: "Dashboard", href: "/account/dashboard" }
19080
+ ]
19081
+ },
19082
+ {
19083
+ title: "Company",
19084
+ links: [
19085
+ { label: "Contact", href: "#" },
19086
+ { label: "Terms", href: "#" },
19087
+ { label: "Privacy", href: "#" }
19088
+ ]
19089
+ }
19090
+ ],
19091
+ copyright: "\xA9 2026 Sample API"
19092
+ }
19093
+ }
19050
19094
  },
19051
19095
  userPanel: {
19052
19096
  enabled: true,
19053
19097
  dashboard: { enabled: true },
19054
19098
  userManagement: { enabled: true },
19055
19099
  plans: {
19056
- enabled: false,
19057
- page: "./user-panel/AccountPlansPage.tsx"
19100
+ enabled: true,
19101
+ page: "./user-panel/AccountPlansPage.tsx",
19102
+ items: [
19103
+ {
19104
+ sku: "free",
19105
+ displayName: "Free",
19106
+ priceAmount: 0,
19107
+ billingPeriod: "month",
19108
+ limitsJson: '{"rateLimit":{"calls":10,"renewalPeriodSeconds":1},"quota":{"calls":1000,"renewalPeriodDays":30}}'
19109
+ },
19110
+ {
19111
+ sku: "developer",
19112
+ displayName: "Developer",
19113
+ priceAmount: 19,
19114
+ billingPeriod: "month",
19115
+ limitsJson: '{"rateLimit":{"calls":50,"renewalPeriodSeconds":1},"quota":{"calls":50000,"renewalPeriodDays":30}}'
19116
+ },
19117
+ {
19118
+ sku: "team",
19119
+ displayName: "Team",
19120
+ priceAmount: 79,
19121
+ billingPeriod: "month",
19122
+ limitsJson: '{"rateLimit":{"calls":200,"renewalPeriodSeconds":1},"quota":{"calls":500000,"renewalPeriodDays":30}}'
19123
+ },
19124
+ {
19125
+ sku: "enterprise",
19126
+ displayName: "Enterprise",
19127
+ priceAmount: -1,
19128
+ billingPeriod: "custom",
19129
+ limitsJson: '{"quota":{"calls":-1}}'
19130
+ }
19131
+ ]
19058
19132
  }
19059
19133
  }
19060
19134
  }
@@ -19842,50 +19916,7 @@ async function ensureTsconfigJson(dir) {
19842
19916
  return true;
19843
19917
  }
19844
19918
  }
19845
- var LANDING_PAGE_SOURCE = `import {
19846
- FeaturesSection,
19847
- HeroSection,
19848
- LandingShell,
19849
- } from "@lukoweb/apitogo-module-landing";
19850
-
19851
- export default function LandingPage() {
19852
- return (
19853
- <LandingShell
19854
- title="Your API, ready for developers"
19855
- description="Documentation, API reference, and a developer portal \u2014 all in one place."
19856
- >
19857
- <HeroSection
19858
- hero={{
19859
- title: "Your API, ready for developers",
19860
- description:
19861
- "Ship docs, interactive API reference, and a developer portal without building from scratch.",
19862
- }}
19863
- primaryAction={{ label: "Get started", href: "/introduction" }}
19864
- secondaryAction={{ label: "API Reference", href: "/api" }}
19865
- />
19866
- <FeaturesSection
19867
- features={[
19868
- {
19869
- title: "Documentation",
19870
- description: "Guides and references for every integration path.",
19871
- icon: "book-open",
19872
- },
19873
- {
19874
- title: "API Reference",
19875
- description: "Interactive OpenAPI explorer with code samples.",
19876
- icon: "code",
19877
- },
19878
- {
19879
- title: "Developer Portal",
19880
- description: "Auth, API keys, and subscription billing built in.",
19881
- icon: "shield-check",
19882
- },
19883
- ]}
19884
- />
19885
- </LandingShell>
19886
- );
19887
- }
19888
- `;
19919
+ var LANDING_PAGE_SOURCE = 'import {\n ApiShowcaseSection,\n FeaturesSection,\n HeroSection,\n LandingShell,\n PlansPreviewSection,\n PricingCtaSection,\n StepsSection,\n} from "@lukoweb/apitogo-module-landing";\n\nexport default function LandingPage() {\n return (\n <LandingShell\n title="Test against a real API before you build one"\n description="Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA."\n >\n <HeroSection\n hero={{\n badge: "SANDBOX API",\n title: "Test against a real API before you build one",\n description:\n "Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA. No setup, no infrastructure.",\n }}\n primaryAction={{ label: "Start for free", href: "/signup" }}\n secondaryAction={{ label: "Read the docs", href: "/introduction" }}\n codeExamples={[\n {\n filename: "Create a key",\n language: "bash",\n code: `$ curl -X POST https://api.sampleapi.dev/v1/keys\n# sk_live_9f2c7a1e...`,\n },\n {\n filename: "Call an endpoint",\n language: "bash",\n code: `$ curl https://api.sampleapi.dev/v1/users/42 \\\\\n -H "Authorization: Bearer sk_live_9f2c..."\n# { "id": 42, "name": "Priya Shah", "plan": "pro" }`,\n },\n ]}\n />\n <FeaturesSection\n section={{\n label: "Everything a real API has",\n title: "Built for testing, not just browsing",\n description:\n "Realistic data, live auth, rate limits, and webhooks \u2014 so you can ship with confidence.",\n }}\n features={[\n {\n title: "Realistic mock data",\n description:\n "Users, orders, products, and posts with relationships and pagination, not lorem ipsum.",\n icon: "boxes",\n },\n {\n title: "Real auth flow",\n description:\n "Every request checks a live API key, so you can test error handling and expiry.",\n icon: "shield-check",\n },\n {\n title: "Rate limiting",\n description:\n "Per-plan request limits and 429 responses, so you can build and test backoff logic.",\n icon: "gauge",\n },\n {\n title: "Webhooks",\n description:\n "Trigger test events and inspect delivered payloads and retries.",\n icon: "webhook",\n },\n {\n title: "OpenAPI & docs",\n description:\n "A full OpenAPI spec and reference docs for every endpoint.",\n icon: "book-open",\n },\n {\n title: "Usage analytics",\n description:\n "Track requests, latency, and errors per key from your dashboard.",\n icon: "activity",\n },\n ]}\n />\n <StepsSection\n steps={[\n {\n step: "01",\n title: "Create an account",\n description:\n "Sign up and land straight in your dashboard \u2014 no approval queue.",\n },\n {\n step: "02",\n title: "Generate a key",\n description:\n "Every key is scoped and rate-limited to your plan from the moment it\'s created.",\n },\n {\n step: "03",\n title: "Call any endpoint",\n description:\n "Users, orders, products, posts, and webhooks \u2014 all documented, all live.",\n },\n ]}\n />\n <ApiShowcaseSection\n apiShowcase={{\n title: "Sample endpoints",\n subtitle:\n "A slice of what\'s available \u2014 see the full reference in the docs.",\n browseAllHref: "/api",\n browseAllLabel: "Browse all APIs",\n layout: "list",\n items: [\n {\n name: "List paginated users",\n path: "/v1/users",\n method: "GET",\n summary: "List paginated users",\n },\n {\n name: "Create a mock order",\n path: "/v1/orders",\n method: "POST",\n summary: "Create a mock order",\n },\n {\n name: "Fetch one product",\n path: "/v1/products/{id}",\n method: "GET",\n summary: "Fetch one product",\n },\n {\n name: "List posts with comments",\n path: "/v1/posts",\n method: "GET",\n summary: "List posts with comments",\n },\n {\n name: "Fire a test webhook event",\n path: "/v1/webhooks/test",\n method: "POST",\n summary: "Fire a test webhook event",\n },\n {\n name: "Delete a mock order",\n path: "/v1/orders/{id}",\n method: "DELETE",\n summary: "Delete a mock order",\n },\n ],\n }}\n />\n <PlansPreviewSection\n plans={[\n {\n name: "Free",\n price: "$0",\n unit: "per month",\n blurb: "1,000 requests/mo, community support.",\n },\n {\n name: "Developer",\n price: "$19",\n unit: "per month",\n blurb: "50,000 requests/mo, webhooks included.",\n featured: true,\n },\n {\n name: "Team",\n price: "$79",\n unit: "per month",\n blurb: "500,000 requests/mo, 5 team seats.",\n },\n {\n name: "Enterprise",\n price: "Custom",\n unit: "talk to us",\n blurb: "Unlimited requests, SSO, SLA.",\n },\n ]}\n />\n <PricingCtaSection\n variant="banner"\n pricingCta={{\n title: "Start testing in minutes",\n description:\n "Free tier includes 1,000 requests a month \u2014 no card required.",\n primaryAction: { label: "Start for free", href: "/signup" },\n }}\n />\n </LandingShell>\n );\n}\n';
19889
19920
  var ACCOUNT_PLANS_PAGE_SOURCE = `export { AccountPlansPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
19890
19921
  `;
19891
19922
  var fileExists2 = async (filePath) => {
@@ -44,12 +44,17 @@ export declare const LandingManifestContentSchema: z.ZodOptional<z.ZodObject<{
44
44
  browseAllHref: z.ZodOptional<z.ZodString>;
45
45
  browseAllLabel: z.ZodOptional<z.ZodString>;
46
46
  autoFromOpenApi: z.ZodOptional<z.ZodBoolean>;
47
+ layout: z.ZodOptional<z.ZodEnum<{
48
+ grid: "grid";
49
+ list: "list";
50
+ }>>;
47
51
  items: z.ZodOptional<z.ZodArray<z.ZodObject<{
48
52
  name: z.ZodString;
49
53
  path: z.ZodString;
50
54
  icon: z.ZodOptional<z.ZodString>;
51
55
  latency: z.ZodOptional<z.ZodString>;
52
56
  summary: z.ZodOptional<z.ZodString>;
57
+ method: z.ZodOptional<z.ZodString>;
53
58
  }, z.core.$strip>>>;
54
59
  }, z.core.$strip>>;
55
60
  pricingCta: z.ZodOptional<z.ZodObject<{
@@ -137,12 +137,17 @@ export declare const DevPortalLocalManifestSchema: z.ZodObject<{
137
137
  browseAllHref: z.ZodOptional<z.ZodString>;
138
138
  browseAllLabel: z.ZodOptional<z.ZodString>;
139
139
  autoFromOpenApi: z.ZodOptional<z.ZodBoolean>;
140
+ layout: z.ZodOptional<z.ZodEnum<{
141
+ grid: "grid";
142
+ list: "list";
143
+ }>>;
140
144
  items: z.ZodOptional<z.ZodArray<z.ZodObject<{
141
145
  name: z.ZodString;
142
146
  path: z.ZodString;
143
147
  icon: z.ZodOptional<z.ZodString>;
144
148
  latency: z.ZodOptional<z.ZodString>;
145
149
  summary: z.ZodOptional<z.ZodString>;
150
+ method: z.ZodOptional<z.ZodString>;
146
151
  }, z.core.$strip>>>;
147
152
  }, z.core.$strip>>;
148
153
  pricingCta: z.ZodOptional<z.ZodObject<{
@@ -63,6 +63,7 @@ export declare const LandingApiShowcaseItemSchema: z.ZodObject<{
63
63
  icon: z.ZodOptional<z.ZodString>;
64
64
  latency: z.ZodOptional<z.ZodString>;
65
65
  summary: z.ZodOptional<z.ZodString>;
66
+ method: z.ZodOptional<z.ZodString>;
66
67
  }, z.core.$strip>;
67
68
  export declare const LandingApiShowcaseSchema: z.ZodOptional<z.ZodObject<{
68
69
  enabled: z.ZodOptional<z.ZodBoolean>;
@@ -71,12 +72,17 @@ export declare const LandingApiShowcaseSchema: z.ZodOptional<z.ZodObject<{
71
72
  browseAllHref: z.ZodOptional<z.ZodString>;
72
73
  browseAllLabel: z.ZodOptional<z.ZodString>;
73
74
  autoFromOpenApi: z.ZodOptional<z.ZodBoolean>;
75
+ layout: z.ZodOptional<z.ZodEnum<{
76
+ grid: "grid";
77
+ list: "list";
78
+ }>>;
74
79
  items: z.ZodOptional<z.ZodArray<z.ZodObject<{
75
80
  name: z.ZodString;
76
81
  path: z.ZodString;
77
82
  icon: z.ZodOptional<z.ZodString>;
78
83
  latency: z.ZodOptional<z.ZodString>;
79
84
  summary: z.ZodOptional<z.ZodString>;
85
+ method: z.ZodOptional<z.ZodString>;
80
86
  }, z.core.$strip>>>;
81
87
  }, z.core.$strip>>;
82
88
  export declare const LandingPricingCtaSchema: z.ZodOptional<z.ZodObject<{
@@ -174,12 +180,17 @@ export declare const LandingContentSchema: z.ZodOptional<z.ZodObject<{
174
180
  browseAllHref: z.ZodOptional<z.ZodString>;
175
181
  browseAllLabel: z.ZodOptional<z.ZodString>;
176
182
  autoFromOpenApi: z.ZodOptional<z.ZodBoolean>;
183
+ layout: z.ZodOptional<z.ZodEnum<{
184
+ grid: "grid";
185
+ list: "list";
186
+ }>>;
177
187
  items: z.ZodOptional<z.ZodArray<z.ZodObject<{
178
188
  name: z.ZodString;
179
189
  path: z.ZodString;
180
190
  icon: z.ZodOptional<z.ZodString>;
181
191
  latency: z.ZodOptional<z.ZodString>;
182
192
  summary: z.ZodOptional<z.ZodString>;
193
+ method: z.ZodOptional<z.ZodString>;
183
194
  }, z.core.$strip>>>;
184
195
  }, z.core.$strip>>;
185
196
  pricingCta: z.ZodOptional<z.ZodObject<{
@@ -303,12 +314,17 @@ export declare const LandingModuleSchema: z.ZodObject<{
303
314
  browseAllHref: z.ZodOptional<z.ZodString>;
304
315
  browseAllLabel: z.ZodOptional<z.ZodString>;
305
316
  autoFromOpenApi: z.ZodOptional<z.ZodBoolean>;
317
+ layout: z.ZodOptional<z.ZodEnum<{
318
+ grid: "grid";
319
+ list: "list";
320
+ }>>;
306
321
  items: z.ZodOptional<z.ZodArray<z.ZodObject<{
307
322
  name: z.ZodString;
308
323
  path: z.ZodString;
309
324
  icon: z.ZodOptional<z.ZodString>;
310
325
  latency: z.ZodOptional<z.ZodString>;
311
326
  summary: z.ZodOptional<z.ZodString>;
327
+ method: z.ZodOptional<z.ZodString>;
312
328
  }, z.core.$strip>>>;
313
329
  }, z.core.$strip>>;
314
330
  pricingCta: z.ZodOptional<z.ZodObject<{
@@ -553,12 +569,17 @@ export declare const ModulesSchema: z.ZodOptional<z.ZodObject<{
553
569
  browseAllHref: z.ZodOptional<z.ZodString>;
554
570
  browseAllLabel: z.ZodOptional<z.ZodString>;
555
571
  autoFromOpenApi: z.ZodOptional<z.ZodBoolean>;
572
+ layout: z.ZodOptional<z.ZodEnum<{
573
+ grid: "grid";
574
+ list: "list";
575
+ }>>;
556
576
  items: z.ZodOptional<z.ZodArray<z.ZodObject<{
557
577
  name: z.ZodString;
558
578
  path: z.ZodString;
559
579
  icon: z.ZodOptional<z.ZodString>;
560
580
  latency: z.ZodOptional<z.ZodString>;
561
581
  summary: z.ZodOptional<z.ZodString>;
582
+ method: z.ZodOptional<z.ZodString>;
562
583
  }, z.core.$strip>>>;
563
584
  }, z.core.$strip>>;
564
585
  pricingCta: z.ZodOptional<z.ZodObject<{
@@ -6933,12 +6933,17 @@ export declare const ZudokuConfig: z.ZodObject<{
6933
6933
  browseAllHref: z.ZodOptional<z.ZodString>;
6934
6934
  browseAllLabel: z.ZodOptional<z.ZodString>;
6935
6935
  autoFromOpenApi: z.ZodOptional<z.ZodBoolean>;
6936
+ layout: z.ZodOptional<z.ZodEnum<{
6937
+ grid: "grid";
6938
+ list: "list";
6939
+ }>>;
6936
6940
  items: z.ZodOptional<z.ZodArray<z.ZodObject<{
6937
6941
  name: z.ZodString;
6938
6942
  path: z.ZodString;
6939
6943
  icon: z.ZodOptional<z.ZodString>;
6940
6944
  latency: z.ZodOptional<z.ZodString>;
6941
6945
  summary: z.ZodOptional<z.ZodString>;
6946
+ method: z.ZodOptional<z.ZodString>;
6942
6947
  }, z.core.$strip>>>;
6943
6948
  }, z.core.$strip>>;
6944
6949
  pricingCta: z.ZodOptional<z.ZodObject<{
@@ -575,12 +575,14 @@ export interface _Schema21 {
575
575
  browseAllHref?: string
576
576
  browseAllLabel?: string
577
577
  autoFromOpenApi?: boolean
578
+ layout?: ("grid" | "list")
578
579
  items?: {
579
580
  name: string
580
581
  path: string
581
582
  icon?: string
582
583
  latency?: string
583
584
  summary?: string
585
+ method?: string
584
586
  }[]
585
587
  }
586
588
  pricingCta?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo",
3
- "version": "0.1.87",
3
+ "version": "0.1.88",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -3,44 +3,45 @@ import type { ZudokuConfig } from "../config/validators/ZudokuConfig.js";
3
3
  /**
4
4
  * APIToGo brand theme for per-site overrides via apitogo.config.
5
5
  * Keep in sync with defaultTheme.css (full palette including sidebar/chart tokens).
6
+ * Sourced from Sample-API-Website-UI (teal brand + navy/mist surfaces).
6
7
  */
7
8
  export const apitogoBrandTheme: NonNullable<ZudokuConfig["theme"]> = {
8
9
  fonts: {
9
- sans: "IBM Plex Sans",
10
+ sans: "Inter",
10
11
  mono: "JetBrains Mono",
11
12
  },
12
13
  light: {
13
- background: "#F5F6F9",
14
- foreground: "#0E1016",
15
- card: "#FFFFFF",
16
- cardForeground: "#0E1016",
17
- primary: "#E11D2A",
14
+ background: "#ffffff",
15
+ foreground: "#252b3b",
16
+ card: "#ffffff",
17
+ cardForeground: "#252b3b",
18
+ primary: "#0d9488",
18
19
  primaryForeground: "#ffffff",
19
- secondary: "#FAFBFC",
20
- secondaryForeground: "#0E1016",
21
- muted: "#FAFBFC",
22
- mutedForeground: "#565E70",
23
- accent: "rgba(225, 29, 42, 0.08)",
24
- accentForeground: "#E11D2A",
25
- border: "#E6E8EF",
26
- input: "#D6DAE3",
27
- ring: "#E11D2A",
20
+ secondary: "#f4f6fb",
21
+ secondaryForeground: "#252b3b",
22
+ muted: "#e9edf5",
23
+ mutedForeground: "rgba(37, 43, 59, 0.55)",
24
+ accent: "rgba(13, 148, 136, 0.1)",
25
+ accentForeground: "#0d9488",
26
+ border: "#d9deea",
27
+ input: "#d9deea",
28
+ ring: "#0d9488",
28
29
  },
29
30
  dark: {
30
- background: "#0B0D12",
31
- foreground: "#EDEFF5",
32
- card: "#14171F",
33
- cardForeground: "#EDEFF5",
34
- primary: "#F23645",
31
+ background: "#252b3b",
32
+ foreground: "#e9edf5",
33
+ card: "#2b3243",
34
+ cardForeground: "#e9edf5",
35
+ primary: "#0d9488",
35
36
  primaryForeground: "#ffffff",
36
- secondary: "#191D27",
37
- secondaryForeground: "#EDEFF5",
38
- muted: "#191D27",
39
- mutedForeground: "#A6AEBF",
40
- accent: "rgba(242, 54, 69, 0.13)",
41
- accentForeground: "#F23645",
42
- border: "#252B37",
43
- input: "#333A48",
44
- ring: "#F23645",
37
+ secondary: "#1e2330",
38
+ secondaryForeground: "#e9edf5",
39
+ muted: "#1e2330",
40
+ mutedForeground: "rgba(217, 222, 234, 0.58)",
41
+ accent: "rgba(13, 148, 136, 0.12)",
42
+ accentForeground: "#0d9488",
43
+ border: "rgba(217, 222, 234, 0.18)",
44
+ input: "#323a4f",
45
+ ring: "#0d9488",
45
46
  },
46
47
  };
@@ -1,77 +1,77 @@
1
1
  /**
2
2
  * Default APIToGo brand theme for scaffolded sites.
3
- * Color tokens sourced from webapp-template/APIToGo.dc.html (:root dark + light override).
3
+ * Color tokens sourced from Sample-API-Website-UI (teal brand + navy/mist surfaces).
4
4
  */
5
5
 
6
6
  :root {
7
7
  --radius: 0.625rem;
8
- --background: #f5f6f9;
9
- --foreground: #0e1016;
8
+ --background: #ffffff;
9
+ --foreground: #252b3b;
10
10
  --card: #ffffff;
11
- --card-foreground: #0e1016;
11
+ --card-foreground: #252b3b;
12
12
  --popover: #ffffff;
13
- --popover-foreground: #0e1016;
14
- --primary: #e11d2a;
13
+ --popover-foreground: #252b3b;
14
+ --primary: #0d9488;
15
15
  --primary-foreground: #ffffff;
16
- --secondary: #fafbfc;
17
- --secondary-foreground: #0e1016;
18
- --muted: #fafbfc;
19
- --muted-foreground: #565e70;
20
- --accent: rgba(225, 29, 42, 0.08);
21
- --accent-foreground: #e11d2a;
22
- --destructive: #e11d2a;
16
+ --secondary: #f4f6fb;
17
+ --secondary-foreground: #252b3b;
18
+ --muted: #e9edf5;
19
+ --muted-foreground: rgba(37, 43, 59, 0.55);
20
+ --accent: rgba(13, 148, 136, 0.1);
21
+ --accent-foreground: #0d9488;
22
+ --destructive: #e02424;
23
23
  --warning: oklch(0.84 0.16 84);
24
24
  --warning-foreground: oklch(0.47 0.14 46.2);
25
- --border: #e6e8ef;
26
- --input: #d6dae3;
27
- --ring: #e11d2a;
28
- --chart-1: #e11d2a;
29
- --chart-2: #0e9e63;
30
- --chart-3: #4e5ac0;
31
- --chart-4: #8a92a3;
32
- --chart-5: #565e70;
25
+ --border: #d9deea;
26
+ --input: #d9deea;
27
+ --ring: #0d9488;
28
+ --chart-1: #0d9488;
29
+ --chart-2: #0e9f6e;
30
+ --chart-3: #3f83f8;
31
+ --chart-4: #8b95af;
32
+ --chart-5: #454e66;
33
33
  --sidebar: #ffffff;
34
- --sidebar-foreground: #0e1016;
35
- --sidebar-primary: #e11d2a;
34
+ --sidebar-foreground: #252b3b;
35
+ --sidebar-primary: #0d9488;
36
36
  --sidebar-primary-foreground: #ffffff;
37
- --sidebar-accent: #fafbfc;
38
- --sidebar-accent-foreground: #0e1016;
39
- --sidebar-border: #e6e8ef;
40
- --sidebar-ring: #e11d2a;
37
+ --sidebar-accent: #f4f6fb;
38
+ --sidebar-accent-foreground: #252b3b;
39
+ --sidebar-border: #d9deea;
40
+ --sidebar-ring: #0d9488;
41
41
  }
42
42
 
43
43
  .dark {
44
- --background: #0b0d12;
45
- --foreground: #edeff5;
46
- --card: #14171f;
47
- --card-foreground: #edeff5;
48
- --popover: #1d222d;
49
- --popover-foreground: #edeff5;
50
- --primary: #f23645;
44
+ --background: #252b3b;
45
+ --foreground: #e9edf5;
46
+ --card: #2b3243;
47
+ --card-foreground: #e9edf5;
48
+ --popover: #2b3243;
49
+ --popover-foreground: #e9edf5;
50
+ --primary: #0d9488;
51
51
  --primary-foreground: #ffffff;
52
- --secondary: #191d27;
53
- --secondary-foreground: #edeff5;
54
- --muted: #191d27;
55
- --muted-foreground: #a6aebf;
56
- --accent: rgba(242, 54, 69, 0.13);
57
- --accent-foreground: #f23645;
58
- --destructive: #f23645;
52
+ --secondary: #1e2330;
53
+ --secondary-foreground: #e9edf5;
54
+ --muted: #1e2330;
55
+ --muted-foreground: rgba(217, 222, 234, 0.58);
56
+ --accent: rgba(13, 148, 136, 0.12);
57
+ --accent-foreground: #0d9488;
58
+ --destructive: #e02424;
59
59
  --warning: oklch(0.41 0.11 46);
60
60
  --warning-foreground: oklch(0.99 0.02 95);
61
- --border: #252b37;
62
- --input: #333a48;
63
- --ring: #f23645;
64
- --chart-1: #f23645;
65
- --chart-2: #16c784;
66
- --chart-3: #aeb9e8;
67
- --chart-4: #6b7385;
68
- --chart-5: #a6aebf;
69
- --sidebar: #0e1119;
70
- --sidebar-foreground: #edeff5;
71
- --sidebar-primary: #f23645;
61
+ --border: rgba(217, 222, 234, 0.18);
62
+ --input: #323a4f;
63
+ --ring: #0d9488;
64
+ --chart-1: #0d9488;
65
+ --chart-2: #0e9f6e;
66
+ --chart-3: #3f83f8;
67
+ --chart-4: #8b95af;
68
+ --chart-5: #b9c1d6;
69
+ --sidebar: #1e2330;
70
+ --sidebar-foreground: #e9edf5;
71
+ --sidebar-primary: #0d9488;
72
72
  --sidebar-primary-foreground: #ffffff;
73
- --sidebar-accent: #191d27;
74
- --sidebar-accent-foreground: #edeff5;
75
- --sidebar-border: #252b37;
76
- --sidebar-ring: #f23645;
73
+ --sidebar-accent: #252b3b;
74
+ --sidebar-accent-foreground: #e9edf5;
75
+ --sidebar-border: rgba(217, 222, 234, 0.18);
76
+ --sidebar-ring: #0d9488;
77
77
  }
@@ -94,6 +94,7 @@ export const LandingApiShowcaseItemSchema = z.object({
94
94
  icon: z.string().optional(),
95
95
  latency: z.string().optional(),
96
96
  summary: z.string().optional(),
97
+ method: z.string().optional(),
97
98
  });
98
99
 
99
100
  export const LandingApiShowcaseSchema = z
@@ -104,6 +105,7 @@ export const LandingApiShowcaseSchema = z
104
105
  browseAllHref: z.string().optional(),
105
106
  browseAllLabel: z.string().optional(),
106
107
  autoFromOpenApi: z.boolean().optional(),
108
+ layout: z.enum(["grid", "list"]).optional(),
107
109
  items: z.array(LandingApiShowcaseItemSchema).optional(),
108
110
  })
109
111
  .optional();
@@ -1,4 +1,5 @@
1
1
  import { Spinner } from "@lukoweb/apitogo/components";
2
+ import { Badge } from "@lukoweb/apitogo/ui/Badge.js";
2
3
  import { Button } from "@lukoweb/apitogo/ui/Button.js";
3
4
  import {
4
5
  Card,
@@ -9,7 +10,7 @@ import {
9
10
  } from "@lukoweb/apitogo/ui/Card.js";
10
11
  import { Input } from "@lukoweb/apitogo/ui/Input.js";
11
12
  import { Label } from "@lukoweb/apitogo/ui/Label.js";
12
- import { useCallback, useEffect, useState } from "react";
13
+ import { useCallback, useEffect, useState, type ReactNode } from "react";
13
14
  import { Link, useSearchParams } from "react-router";
14
15
  import type { DevPortalAuthConfigResponse } from "./dev-portal-constants.js";
15
16
  import {
@@ -31,20 +32,31 @@ import {
31
32
  type AuthPageShellProps = {
32
33
  title: string;
33
34
  description?: string;
34
- children: React.ReactNode;
35
+ badge?: ReactNode;
36
+ children: ReactNode;
35
37
  };
36
38
 
37
- function AuthPageShell({ title, description, children }: AuthPageShellProps) {
39
+ function AuthPageShell({
40
+ title,
41
+ description,
42
+ badge,
43
+ children,
44
+ }: AuthPageShellProps) {
38
45
  return (
39
- <div className="flex items-center justify-center mt-8 px-4">
40
- <Card className="max-w-md w-full">
41
- <CardHeader>
42
- <CardTitle className="text-lg">{title}</CardTitle>
46
+ <div className="flex items-center justify-center bg-muted px-5 py-10">
47
+ <Card className="w-full max-w-md gap-0 bg-card py-8">
48
+ <CardHeader className="gap-0 px-8 pb-0">
49
+ {badge ? <div className="mb-3.5">{badge}</div> : null}
50
+ <CardTitle className="text-2xl font-bold tracking-tight">
51
+ {title}
52
+ </CardTitle>
43
53
  {description ? (
44
- <CardDescription>{description}</CardDescription>
54
+ <CardDescription className="mt-1 mb-[22px] text-sm">
55
+ {description}
56
+ </CardDescription>
45
57
  ) : null}
46
58
  </CardHeader>
47
- <CardContent className="space-y-4">{children}</CardContent>
59
+ <CardContent className="space-y-4 px-8">{children}</CardContent>
48
60
  </Card>
49
61
  </div>
50
62
  );
@@ -349,7 +361,7 @@ export function DevPortalRegisterPage({
349
361
  if (!baseUrl) {
350
362
  return (
351
363
  <AuthPageShell
352
- title="Create account"
364
+ title="Create your account"
353
365
  description="Dev portal API is not configured."
354
366
  >
355
367
  <p className="text-sm text-muted-foreground">
@@ -361,16 +373,17 @@ export function DevPortalRegisterPage({
361
373
 
362
374
  return (
363
375
  <AuthPageShell
364
- title="Create account"
365
- description="Register for API access."
376
+ title="Create your account"
366
377
  >
367
- <form className="space-y-4" onSubmit={onSubmit}>
378
+ <form className="flex flex-col gap-4" onSubmit={onSubmit}>
368
379
  <div className="space-y-2">
369
- <Label htmlFor="displayName">Name</Label>
380
+ <Label htmlFor="displayName">Full name</Label>
370
381
  <Input
371
382
  id="displayName"
383
+ placeholder="Priya Shah"
372
384
  value={displayName}
373
385
  onChange={(event) => setDisplayName(event.target.value)}
386
+ className="h-10"
374
387
  />
375
388
  </div>
376
389
  <div className="space-y-2">
@@ -379,9 +392,11 @@ export function DevPortalRegisterPage({
379
392
  id="email"
380
393
  type="email"
381
394
  autoComplete="email"
395
+ placeholder="you@company.com"
382
396
  value={email}
383
397
  onChange={(event) => setEmail(event.target.value)}
384
398
  required
399
+ className="h-10"
385
400
  />
386
401
  </div>
387
402
  <div className="space-y-2">
@@ -390,24 +405,36 @@ export function DevPortalRegisterPage({
390
405
  id="password"
391
406
  type="password"
392
407
  autoComplete="new-password"
408
+ placeholder="••••••••"
393
409
  value={password}
394
410
  onChange={(event) => setPassword(event.target.value)}
395
411
  required
412
+ className="h-10"
396
413
  />
414
+ <p className="text-xs text-muted-foreground">At least 8 characters</p>
397
415
  </div>
398
416
  <AuthError message={error} />
399
417
  {message ? (
400
418
  <p className="text-sm text-muted-foreground">{message}</p>
401
419
  ) : null}
402
- <Button type="submit" className="w-full" disabled={pending}>
420
+ <Button
421
+ type="submit"
422
+ size="lg"
423
+ className="h-12 w-full text-base"
424
+ disabled={pending}
425
+ >
403
426
  {pending ? <Spinner /> : "Create account"}
404
427
  </Button>
405
- <p className="text-sm text-center">
428
+ <p className="text-xs leading-relaxed text-muted-foreground">
429
+ By creating an account you agree to our Terms and Privacy Policy.
430
+ </p>
431
+ <p className="text-sm text-muted-foreground">
432
+ Already have an account?{" "}
406
433
  <Link
407
- className="text-primary hover:underline"
434
+ className="font-semibold text-primary no-underline hover:underline"
408
435
  to={`/login?redirect=${encodeURIComponent(returnUrl)}`}
409
436
  >
410
- Back to sign in
437
+ Log in
411
438
  </Link>
412
439
  </p>
413
440
  </form>
@@ -148,6 +148,7 @@ export const Header = memo(function HeaderInner() {
148
148
  const authPlacement = header?.placements?.auth ?? "end";
149
149
  const authPosition =
150
150
  authPlacement === "navigation" ? navPosition : authPlacement;
151
+ const hasMarketingHeaderNav = (header?.navigation?.length ?? 0) > 0;
151
152
 
152
153
  const logoLightSrc = site?.logo
153
154
  ? /https?:\/\//.test(site.logo.src.light)
@@ -206,11 +207,13 @@ export const Header = memo(function HeaderInner() {
206
207
  </div>
207
208
  </Link>
208
209
  <Slot.Target name="head-navigation-start" />
209
- <div className="hidden lg:flex min-w-0 items-center ml-2">
210
- <Slot.Target name="top-navigation-before" />
211
- <TopNavigation />
212
- <Slot.Target name="top-navigation-after" />
213
- </div>
210
+ {!hasMarketingHeaderNav ? (
211
+ <div className="hidden lg:flex min-w-0 items-center ml-2">
212
+ <Slot.Target name="top-navigation-before" />
213
+ <TopNavigation />
214
+ <Slot.Target name="top-navigation-after" />
215
+ </div>
216
+ ) : null}
214
217
  {searchPosition === "start" && (
215
218
  <Search className="hidden lg:flex" />
216
219
  )}
@@ -1,5 +1,4 @@
1
1
  import { Button } from "@lukoweb/apitogo/ui/Button.js";
2
- import { ArrowRightIcon } from "lucide-react";
3
2
  import { useAuth } from "../authentication/hook.js";
4
3
  import { cn } from "../util/cn.js";
5
4
 
@@ -15,19 +14,18 @@ export const HeaderAuthActions = ({ className }: { className?: string }) => {
15
14
  <div className={cn("hidden lg:flex items-center gap-2.5", className)}>
16
15
  <Button
17
16
  variant="ghost"
18
- size="lg"
17
+ size="sm"
19
18
  className="font-semibold text-foreground hover:text-primary px-3"
20
19
  onClick={() => void auth.login()}
21
20
  >
22
- Sign in
21
+ Log in
23
22
  </Button>
24
23
  <Button
25
- size="lg"
26
- className="gap-1.5 rounded-[9px] px-4 font-semibold shadow-[0_4px_14px_-4px] shadow-primary/30"
24
+ size="sm"
25
+ className="rounded-[9px] px-4 font-semibold shadow-[0_4px_14px_-4px] shadow-primary/30"
27
26
  onClick={() => void auth.signup()}
28
27
  >
29
- Start free
30
- <ArrowRightIcon size={15} strokeWidth={2} />
28
+ Get started
31
29
  </Button>
32
30
  </div>
33
31
  );
@@ -4,7 +4,10 @@ import { Outlet } from "react-router";
4
4
  import { cn } from "../util/cn.js";
5
5
  import { useScrollToAnchor } from "../util/useScrollToAnchor.js";
6
6
  import { useScrollToTop } from "../util/useScrollToTop.js";
7
- import { useZudoku } from "./context/ZudokuContext.js";
7
+ import {
8
+ useCurrentNavigation,
9
+ useZudoku,
10
+ } from "./context/ZudokuContext.js";
8
11
  import { Footer } from "./Footer.js";
9
12
  import { Header } from "./Header.js";
10
13
  import { Main } from "./Main.js";
@@ -19,6 +22,8 @@ const LoadingFallback = () => (
19
22
 
20
23
  export const Layout = ({ children }: { children?: ReactNode }) => {
21
24
  const { authentication } = useZudoku();
25
+ const { navigation } = useCurrentNavigation();
26
+ const hasSideNav = navigation.length > 0;
22
27
 
23
28
  useScrollToAnchor();
24
29
  useScrollToTop();
@@ -38,9 +43,13 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
38
43
 
39
44
  <div
40
45
  className={cn(
41
- "grid max-w-screen-2xl w-full lg:mx-auto",
42
- "[&:has(>:only-child)]:grid-rows-1 grid-rows-[0_min-content_1fr] lg:grid-rows-[min-content_1fr]",
43
- "grid-cols-1 lg:grid-cols-[var(--side-nav-width)_1fr]",
46
+ "grid w-full",
47
+ // Docs shell: cap width + reserve side-nav column.
48
+ // Marketing pages (no side nav): full-bleed so section backgrounds
49
+ // and compare/FAQ bands can span the viewport.
50
+ hasSideNav
51
+ ? "max-w-screen-2xl lg:mx-auto grid-rows-[0_min-content_1fr] lg:grid-rows-[min-content_1fr] grid-cols-1 lg:grid-cols-[var(--side-nav-width)_1fr]"
52
+ : "grid-cols-1 grid-rows-1",
44
53
  )}
45
54
  >
46
55
  <Suspense fallback={<LoadingFallback />}>
@@ -37,8 +37,9 @@ export const Main = ({ children }: PropsWithChildren) => {
37
37
  <main
38
38
  data-pagefind-body
39
39
  className={cn(
40
- "px-4 lg:pe-8 lg:px-8",
41
- !hasNavigation && "col-span-full",
40
+ // Docs: gutter so content clears the side nav.
41
+ // Marketing: no shell padding — pages own section px / full-bleed bands.
42
+ hasNavigation ? "px-4 lg:pe-8 lg:px-8" : "col-span-full px-0",
42
43
  isNavigating && "animate-pulse",
43
44
  )}
44
45
  >
@@ -3,7 +3,6 @@ import { Separator } from "@lukoweb/apitogo/ui/Separator.js";
3
3
  import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
4
4
  import { deepEqual } from "fast-equals";
5
5
  import {
6
- ArrowRightIcon,
7
6
  ChevronDownIcon,
8
7
  LogOutIcon,
9
8
  MenuIcon,
@@ -258,17 +257,15 @@ export const MobileTopNavigation = () => {
258
257
  void login();
259
258
  }}
260
259
  >
261
- Sign in
260
+ Log in
262
261
  </Button>
263
262
  <Button
264
- className="gap-1.5"
265
263
  onClick={() => {
266
264
  setDrawerOpen(false);
267
265
  void signup();
268
266
  }}
269
267
  >
270
- Start free
271
- <ArrowRightIcon size={15} strokeWidth={2} />
268
+ Get started
272
269
  </Button>
273
270
  </div>
274
271
  ))}
@@ -1,18 +1,25 @@
1
- import { MoonIcon, SunIcon } from "lucide-react";
1
+ import { MoonIcon, MonitorIcon, SunIcon } from "lucide-react";
2
2
  import { useTheme } from "next-themes";
3
3
  import { focusRing } from "../ui/util.js";
4
4
  import { cn } from "../util/cn.js";
5
5
  import { useIsClient } from "./ClientOnly.js";
6
6
 
7
+ const COMPACT_OPTIONS = [
8
+ { value: "light", label: "Light", Icon: SunIcon },
9
+ { value: "dark", label: "Dark", Icon: MoonIcon },
10
+ { value: "system", label: "System", Icon: MonitorIcon },
11
+ ] as const;
12
+
7
13
  export const ThemeSwitch = ({
8
14
  variant = "default",
9
15
  }: {
10
16
  variant?: "default" | "compact";
11
17
  }) => {
12
- const { resolvedTheme, setTheme } = useTheme();
18
+ const { theme, resolvedTheme, setTheme } = useTheme();
13
19
  const isClient = useIsClient();
14
20
 
15
- const theme = isClient ? resolvedTheme : undefined;
21
+ const activeTheme = isClient ? resolvedTheme : undefined;
22
+ const selectedMode = isClient ? (theme ?? "system") : "system";
16
23
 
17
24
  const handleThemeToggle = () => {
18
25
  setTheme(resolvedTheme === "dark" ? "light" : "dark");
@@ -20,27 +27,36 @@ export const ThemeSwitch = ({
20
27
 
21
28
  if (variant === "compact") {
22
29
  return (
23
- <button
24
- type="button"
30
+ <div
31
+ role="group"
32
+ aria-label="Theme"
25
33
  className={cn(
26
- "grid size-[38px] place-items-center rounded-[9px] border border-border text-muted-foreground transition-colors hover:border-border hover:text-foreground",
34
+ "inline-flex h-8 items-center gap-0.5 rounded-[9px] border border-border bg-muted/40 p-0.5",
27
35
  focusRing,
28
36
  )}
29
- onClick={handleThemeToggle}
30
- aria-label={
31
- !isClient
32
- ? "Toggle theme"
33
- : theme === "dark"
34
- ? "Switch to light mode"
35
- : "Switch to dark mode"
36
- }
37
37
  >
38
- {!isClient || theme === "dark" ? (
39
- <SunIcon size={17} strokeWidth={1.8} />
40
- ) : (
41
- <MoonIcon size={17} strokeWidth={1.8} />
42
- )}
43
- </button>
38
+ {COMPACT_OPTIONS.map(({ value, label, Icon }) => {
39
+ const selected = selectedMode === value;
40
+ return (
41
+ <button
42
+ key={value}
43
+ type="button"
44
+ className={cn(
45
+ "grid size-7 place-items-center rounded-[7px] text-muted-foreground transition-colors",
46
+ selected
47
+ ? "bg-card text-foreground shadow-xs"
48
+ : "hover:text-foreground",
49
+ )}
50
+ onClick={() => setTheme(value)}
51
+ aria-label={label}
52
+ aria-pressed={selected}
53
+ title={label}
54
+ >
55
+ <Icon size={14} strokeWidth={1.8} />
56
+ </button>
57
+ );
58
+ })}
59
+ </div>
44
60
  );
45
61
  }
46
62
 
@@ -52,7 +68,7 @@ export const ThemeSwitch = ({
52
68
  aria-label={
53
69
  !isClient
54
70
  ? "Toggle theme"
55
- : theme === "dark"
71
+ : activeTheme === "dark"
56
72
  ? "Switch to light mode"
57
73
  : "Switch to dark mode"
58
74
  }
@@ -60,8 +76,8 @@ export const ThemeSwitch = ({
60
76
  <div
61
77
  className={cn(
62
78
  "rounded-full p-0.5 border border-transparent [&>svg>circle]:transition-colors [&>svg>path]:transition-transform transition-all [&>svg>path]:duration-200 [&>svg>circle]:duration-500 [&>svg>circle]:fill-transparent",
63
- theme === "light" && "border-border bg-muted",
64
- theme === "dark" &&
79
+ activeTheme === "light" && "border-border bg-muted",
80
+ activeTheme === "dark" &&
65
81
  "group-hover:[&>svg>path]:scale-110 group-hover:[&>svg>path]:-translate-x-px group-hover:[&>svg>path]:-translate-y-px group-hover:rotate-15",
66
82
  )}
67
83
  >
@@ -70,9 +86,9 @@ export const ThemeSwitch = ({
70
86
  <div
71
87
  className={cn(
72
88
  "rounded-full p-0.5 border border-transparent transition-transform transform-gpu duration-500",
73
- theme === "dark" &&
89
+ activeTheme === "dark" &&
74
90
  "border-border bg-muted [&>svg>path]:fill-white [&>svg>path]:stroke-transparent",
75
- theme === "light" &&
91
+ activeTheme === "light" &&
76
92
  "group-hover:rotate-[-10deg] [&>svg>path]:stroke-currentColor",
77
93
  )}
78
94
  >