scale-stack 0.0.1 → 0.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scale-stack",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "SKALSTÅKK — flat-packed code efficiency delivered straight to your terminal",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.33.0",
@@ -1,7 +1,6 @@
1
1
  import type { Metadata } from "next";
2
2
  <% if (i18n) { %>import { getTranslations } from "next-intl/server";
3
- import { hasLocale } from "next-intl";
4
- import { routing } from "@/i18n/routing";
3
+ import type { Locale } from "next-intl";
5
4
  <% } %>
6
5
 
7
6
  <% if (i18n) { %>type ChatLayoutProps = Readonly<{
@@ -12,10 +11,7 @@ import { routing } from "@/i18n/routing";
12
11
  export async function generateMetadata({
13
12
  params,
14
13
  }: Pick<ChatLayoutProps, "params">): Promise<Metadata> {
15
- const { locale: requestedLocale } = await params;
16
- const locale = hasLocale(routing.locales, requestedLocale)
17
- ? requestedLocale
18
- : routing.defaultLocale;
14
+ const locale = (await params).locale as Locale;
19
15
  const t = await getTranslations({ locale, namespace: "Chat" });
20
16
 
21
17
  return {
@@ -1,10 +1,16 @@
1
1
  <% if (i18n) { %>import { getTranslations } from "next-intl/server";
2
+ import type { Locale } from "next-intl";
2
3
 
3
4
  <% } %>
4
5
  import { ChatPanel } from "./_components/ChatPanel";
5
6
 
6
- export default <% if (i18n) { %>async <% } %>function ChatPage() {
7
- <% if (i18n) { %> const t = await getTranslations("Chat");
7
+ <% if (i18n) { %>type ChatPageProps = Readonly<{
8
+ params: Promise<{ locale: Locale }>;
9
+ }>;
10
+
11
+ <% } %>export default <% if (i18n) { %>async <% } %>function ChatPage(<% if (i18n) { %>{ params }: ChatPageProps<% } %>) {
12
+ <% if (i18n) { %> const { locale } = await params;
13
+ const t = await getTranslations({ locale, namespace: "Chat" });
8
14
 
9
15
  <% } %>
10
16
  return (
@@ -1,6 +1,10 @@
1
1
  import type { Metadata } from "next";
2
2
  import { Geist, Geist_Mono } from "next/font/google";
3
3
  import "./globals.css";
4
+ <% if (includeClientSideWrappers) { %>
5
+ import { Suspense } from "react";
6
+ import { ClientSideWrappers } from "./_providers/client-side-wrappers";
7
+ <% } %>
4
8
 
5
9
  const geistSans = Geist({
6
10
  variable: "--font-geist-sans",
@@ -28,8 +32,14 @@ export default function RootLayout({
28
32
  className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
29
33
  >
30
34
  <body className="min-h-full flex flex-col">
35
+ <% if (includeClientSideWrappers) { %>
36
+ <Suspense>
37
+ <ClientSideWrappers>{children}</ClientSideWrappers>
38
+ </Suspense>
39
+ <% } else { %>
31
40
  {/* Scale Stack: wrap with NuqsAdapter and other root providers under src/app/_providers/ */}
32
41
  {children}
42
+ <% } %>
33
43
  </body>
34
44
  </html>
35
45
  );
@@ -1,10 +1,16 @@
1
1
  <% if (i18n) { %>import { getTranslations } from "next-intl/server";
2
+ import type { Locale } from "next-intl";
2
3
 
3
4
  <% } %>
4
5
  import { ExampleForm } from "./_components/ExampleForm";
5
6
 
6
- export default <% if (i18n) { %>async <% } %>function DashboardPage() {
7
- <% if (i18n) { %> const t = await getTranslations("Dashboard");
7
+ <% if (i18n) { %>type DashboardPageProps = Readonly<{
8
+ params: Promise<{ locale: Locale }>;
9
+ }>;
10
+
11
+ <% } %>export default <% if (i18n) { %>async <% } %>function DashboardPage(<% if (i18n) { %>{ params }: DashboardPageProps<% } %>) {
12
+ <% if (i18n) { %> const { locale } = await params;
13
+ const t = await getTranslations({ locale, namespace: "Dashboard" });
8
14
 
9
15
  <% } %>
10
16
  return (
@@ -1,21 +1,45 @@
1
1
  import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
2
3
  import { notFound } from "next/navigation";
4
+ import { Suspense } from "react";
3
5
  import { NextIntlClientProvider, hasLocale } from "next-intl";
4
- import { getMessages, getTranslations } from "next-intl/server";
6
+ import {
7
+ getMessages,
8
+ getTranslations,
9
+ setRequestLocale,
10
+ } from "next-intl/server";
5
11
  import { routing } from "@/i18n/routing";
12
+ import "../globals.css";
13
+ import { ClientSideWrappers } from "../_providers/client-side-wrappers";
14
+
15
+ const geistSans = Geist({
16
+ variable: "--font-geist-sans",
17
+ subsets: ["latin"],
18
+ });
19
+
20
+ const geistMono = Geist_Mono({
21
+ variable: "--font-geist-mono",
22
+ subsets: ["latin"],
23
+ });
6
24
 
7
25
  type LocaleLayoutProps = Readonly<{
8
26
  children: React.ReactNode;
9
27
  params: Promise<{ locale: string }>;
10
28
  }>;
11
29
 
30
+ export function generateStaticParams() {
31
+ return routing.locales.map((locale) => ({ locale }));
32
+ }
33
+
12
34
  export async function generateMetadata({
13
35
  params,
14
36
  }: Pick<LocaleLayoutProps, "params">): Promise<Metadata> {
15
- const { locale: requestedLocale } = await params;
16
- const locale = hasLocale(routing.locales, requestedLocale)
17
- ? requestedLocale
18
- : routing.defaultLocale;
37
+ const { locale } = await params;
38
+
39
+ if (!hasLocale(routing.locales, locale)) {
40
+ notFound();
41
+ }
42
+
19
43
  const t = await getTranslations({ locale, namespace: "Metadata" });
20
44
 
21
45
  return {
@@ -34,12 +58,24 @@ export default async function LocaleLayout({
34
58
  notFound();
35
59
  }
36
60
 
61
+ setRequestLocale(locale);
62
+
37
63
  const messages = await getMessages();
38
64
  const direction = locale === "ar" ? "rtl" : "ltr";
39
65
 
40
66
  return (
41
- <NextIntlClientProvider messages={messages}>
42
- <div dir={direction}>{children}</div>
43
- </NextIntlClientProvider>
67
+ <html
68
+ lang={locale}
69
+ dir={direction}
70
+ className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
71
+ >
72
+ <body className="min-h-full flex flex-col">
73
+ <NextIntlClientProvider locale={locale} messages={messages}>
74
+ <Suspense>
75
+ <ClientSideWrappers>{children}</ClientSideWrappers>
76
+ </Suspense>
77
+ </NextIntlClientProvider>
78
+ </body>
79
+ </html>
44
80
  );
45
81
  }
@@ -1,4 +1,5 @@
1
1
  <% if (i18n) { %>import { getTranslations } from "next-intl/server";
2
+ import type { Locale } from "next-intl";
2
3
  import { Link } from "@/i18n/navigation";
3
4
  <% } else { %>import Link from "next/link";
4
5
  <% } %>import { Button } from "@/components/ui/button";
@@ -10,8 +11,13 @@ import {
10
11
  CardTitle,
11
12
  } from "@/components/ui/card";
12
13
 
13
- export default <% if (i18n) { %>async <% } %>function Home() {
14
- <% if (i18n) { %> const t = await getTranslations("Home");
14
+ <% if (i18n) { %>type HomeProps = Readonly<{
15
+ params: Promise<{ locale: Locale }>;
16
+ }>;
17
+
18
+ <% } %>export default <% if (i18n) { %>async <% } %>function Home(<% if (i18n) { %>{ params }: HomeProps<% } %>) {
19
+ <% if (i18n) { %> const { locale } = await params;
20
+ const t = await getTranslations({ locale, namespace: "Home" });
15
21
 
16
22
  <% } %>
17
23
  return (
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: date-fns
3
+ description: Use date-fns for date parsing, formatting, comparison, and arithmetic in this project. Use when working with dates, durations, calendars, relative time, or locale-aware date formatting.
4
+ ---
5
+
6
+ # date-fns
7
+
8
+ ## Instructions
9
+
10
+ - Use focused function imports from `date-fns`, for example `import { format, parseISO } from "date-fns"`.
11
+ - Keep parsing explicit. Use `parseISO` for ISO strings instead of relying on ambiguous `Date` parsing.
12
+ - Treat `Date` values as values passed between helpers. Do not mutate dates in place.
13
+ - Prefer date-fns helpers for comparison and arithmetic instead of manual millisecond math.
14
+ - Use locale-aware formatting when user-facing dates need localization.
15
+
16
+ ## Common Patterns
17
+
18
+ ```ts
19
+ import { format, isAfter, parseISO } from "date-fns";
20
+
21
+ const dueDate = parseISO(task.dueAt);
22
+ const isOverdue = isAfter(new Date(), dueDate);
23
+ const label = format(dueDate, "PPP");
24
+ ```
25
+
26
+ For business rules, keep date operations near the domain logic and name the intent:
27
+
28
+ ```ts
29
+ import { addDays, startOfDay } from "date-fns";
30
+
31
+ export function trialEndsAt(startedAt: Date): Date {
32
+ return startOfDay(addDays(startedAt, 14));
33
+ }
34
+ ```
@@ -0,0 +1,234 @@
1
+ ---
2
+ name: motion
3
+ description: Use Motion for React animations in this project. Use when adding or changing React animations, gestures, layout animations, page transitions, scroll effects, or exit animations with the `motion` package.
4
+ ---
5
+
6
+ # Motion
7
+
8
+ ## When To Use Motion
9
+
10
+ Use Motion when the UI needs React-aware animation:
11
+
12
+ - State-driven animations linked to component props or state.
13
+ - Cross-device gestures such as hover, tap, focus, drag, and in-view effects.
14
+ - Enter and exit animations for conditionally rendered UI.
15
+ - Layout animations, shared element transitions, reorderable lists, or expanding panels.
16
+ - Coordinated parent/child sequences, staggered reveals, or keyframes.
17
+ - Scroll-triggered and scroll-linked animation.
18
+ - SVG path, shape, or attribute animation.
19
+
20
+ Use CSS transitions instead for a simple, isolated effect like a color change on hover.
21
+
22
+ ## Project Rules
23
+
24
+ - Import React APIs from `motion/react`.
25
+ - In the Next.js App Router, put interactive Motion usage in Client Components. Add `"use client"` when using Motion hooks, gestures, state, event handlers, or browser-only APIs.
26
+ - Prefer transforms and opacity for smooth animation. Use `layout` for layout changes instead of manually animating layout properties.
27
+ - Keep animation targets readable and typed. Extract repeated variants/transitions into local constants near the component that owns them.
28
+ - Respect reduced motion. Replace large transform, parallax, and autoplaying motion with opacity or static alternatives.
29
+ - Avoid adding another animation library unless Motion cannot solve the requirement.
30
+
31
+ ## Import Guide
32
+
33
+ ```tsx
34
+ import {
35
+ AnimatePresence,
36
+ MotionConfig,
37
+ motion,
38
+ stagger,
39
+ useAnimate,
40
+ useReducedMotion,
41
+ useScroll,
42
+ useTransform,
43
+ } from "motion/react";
44
+ ```
45
+
46
+ Use only what you need so the package can stay tree-shakable.
47
+
48
+ ## Decision Guide
49
+
50
+ - One element changes with state: use `animate`, `initial`, and `transition`.
51
+ - Hover/tap/focus/drag/in-view behavior: use `whileHover`, `whileTap`, `whileFocus`, `whileDrag`, or `whileInView`.
52
+ - Element leaves the React tree: wrap the conditional region in `AnimatePresence` and define `exit`.
53
+ - Parent and children need coordination: use `variants`, `stagger`, `delayChildren`, and `when`.
54
+ - Element size or position changes: use `layout`; use `layoutId` for shared element transitions.
55
+ - Animation must run from an event or sequence outside render state: use `useAnimate`.
56
+ - Animation follows scroll: use `useScroll` and `useTransform`, with a reduced-motion fallback.
57
+
58
+ ## Patterns
59
+
60
+ ### Microinteraction
61
+
62
+ ```tsx
63
+ "use client";
64
+
65
+ import { motion } from "motion/react";
66
+
67
+ export function SaveButton() {
68
+ return (
69
+ <motion.button
70
+ whileHover={{ scale: 1.03 }}
71
+ whileTap={{ scale: 0.97 }}
72
+ transition={{ type: "spring", stiffness: 500, damping: 30 }}
73
+ >
74
+ Save
75
+ </motion.button>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ### Enter And Exit
81
+
82
+ Use `AnimatePresence` when elements leave the React tree. Give exiting children stable keys.
83
+
84
+ ```tsx
85
+ "use client";
86
+
87
+ import { AnimatePresence, motion } from "motion/react";
88
+
89
+ export function Toast({ message }: { message?: string }) {
90
+ return (
91
+ <AnimatePresence>
92
+ {message ? (
93
+ <motion.div
94
+ key={message}
95
+ initial={{ opacity: 0, y: 8 }}
96
+ animate={{ opacity: 1, y: 0 }}
97
+ exit={{ opacity: 0, y: -8 }}
98
+ >
99
+ {message}
100
+ </motion.div>
101
+ ) : null}
102
+ </AnimatePresence>
103
+ );
104
+ }
105
+ ```
106
+
107
+ ### Staggered Lists
108
+
109
+ Use variants when child animations should be coordinated by the parent.
110
+
111
+ ```tsx
112
+ "use client";
113
+
114
+ import { motion, stagger } from "motion/react";
115
+
116
+ const list = {
117
+ hidden: { opacity: 0 },
118
+ visible: {
119
+ opacity: 1,
120
+ transition: { delayChildren: stagger(0.06), when: "beforeChildren" },
121
+ },
122
+ };
123
+
124
+ const item = {
125
+ hidden: { opacity: 0, y: 8 },
126
+ visible: { opacity: 1, y: 0 },
127
+ };
128
+
129
+ export function ResultList({ results }: { results: string[] }) {
130
+ return (
131
+ <motion.ul initial="hidden" animate="visible" variants={list}>
132
+ {results.map((result) => (
133
+ <motion.li key={result} variants={item}>
134
+ {result}
135
+ </motion.li>
136
+ ))}
137
+ </motion.ul>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ### Layout Animation
143
+
144
+ Use `layout` for size and position changes. Use `layoutId` only for intentional shared-element transitions.
145
+
146
+ ```tsx
147
+ "use client";
148
+
149
+ import { motion } from "motion/react";
150
+
151
+ export function ExpandingCard({ expanded }: { expanded: boolean }) {
152
+ return (
153
+ <motion.article layout className={expanded ? "col-span-2" : undefined}>
154
+ <motion.h2 layout="position">Details</motion.h2>
155
+ </motion.article>
156
+ );
157
+ }
158
+ ```
159
+
160
+ ### Scroll-Linked Animation
161
+
162
+ Use Motion values for scroll-linked transforms, and disable physical movement for reduced-motion users.
163
+
164
+ ```tsx
165
+ "use client";
166
+
167
+ import {
168
+ motion,
169
+ useReducedMotion,
170
+ useScroll,
171
+ useTransform,
172
+ } from "motion/react";
173
+
174
+ export function ReadingProgress() {
175
+ const shouldReduceMotion = useReducedMotion();
176
+ const { scrollYProgress } = useScroll();
177
+ const scaleX = useTransform(scrollYProgress, [0, 1], [0, 1]);
178
+
179
+ return (
180
+ <motion.div
181
+ aria-hidden="true"
182
+ style={{ scaleX: shouldReduceMotion ? 1 : scaleX }}
183
+ className="fixed inset-x-0 top-0 h-1 origin-left"
184
+ />
185
+ );
186
+ }
187
+ ```
188
+
189
+ ### Imperative Sequence
190
+
191
+ Reach for `useAnimate` only when declarative props or variants are not enough.
192
+
193
+ ```tsx
194
+ "use client";
195
+
196
+ import { useEffect } from "react";
197
+ import { useAnimate } from "motion/react";
198
+
199
+ export function IntroSequence() {
200
+ const [scope, animate] = useAnimate();
201
+
202
+ useEffect(() => {
203
+ const controls = animate([
204
+ ["h1", { opacity: [0, 1], y: [12, 0] }],
205
+ ["p", { opacity: [0, 1] }, { at: "-0.1" }],
206
+ ]);
207
+
208
+ return () => controls.stop();
209
+ }, [animate]);
210
+
211
+ return (
212
+ <section ref={scope}>
213
+ <h1>Welcome</h1>
214
+ <p>Everything is ready.</p>
215
+ </section>
216
+ );
217
+ }
218
+ ```
219
+
220
+ ## Accessibility Checklist
221
+
222
+ - Use `MotionConfig reducedMotion="user"` at an app or feature boundary when broad reduced-motion handling is appropriate.
223
+ - Use `useReducedMotion()` for bespoke components.
224
+ - Replace large `x`, `y`, `scale`, rotation, and parallax effects with opacity or static states when reduced motion is enabled.
225
+ - Do not autoplay decorative videos or looping motion for reduced-motion users.
226
+ - Keep focus states visible; do not replace focus indicators with motion-only feedback.
227
+
228
+ ## Performance Checklist
229
+
230
+ - Prefer `opacity` and transforms (`x`, `y`, `scale`, `rotate`) for frequent animations.
231
+ - Avoid animating CSS variables for high-frequency motion because it can trigger paint; prefer Motion values for dynamic transforms.
232
+ - Use `layout` intentionally and sparingly in large lists.
233
+ - Use keyframes for short expressive sequences; keep long-running or repeating animations subtle.
234
+ - Use `initial={false}` when an entrance animation would cause distracting first paint or hydration movement.
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: ts-pattern
3
+ description: Use ts-pattern for exhaustive pattern matching in TypeScript. Use when handling discriminated unions, complex branching, nested data shapes, or replacing fragile switch/if chains.
4
+ ---
5
+
6
+ # ts-pattern
7
+
8
+ ## Instructions
9
+
10
+ - Import `match` and patterns from `ts-pattern`: `import { match, P } from "ts-pattern"`.
11
+ - Prefer `match(value).with(...).exhaustive()` for closed discriminated unions so TypeScript verifies every case.
12
+ - Keep union tags explicit and stable, such as `{ type: "success" }` or `{ status: "idle" }`.
13
+ - Use `P` helpers for nested structures, guards, optionals, and wildcard cases when they improve readability.
14
+ - Avoid using `.otherwise()` for closed unions unless there is a deliberate unknown fallback. Prefer `.exhaustive()`.
15
+
16
+ ## Common Patterns
17
+
18
+ ```ts
19
+ import { match } from "ts-pattern";
20
+
21
+ type Result =
22
+ | { type: "success"; value: string }
23
+ | { type: "error"; message: string }
24
+ | { type: "loading" };
25
+
26
+ export function labelFor(result: Result): string {
27
+ return match(result)
28
+ .with({ type: "success" }, ({ value }) => value)
29
+ .with({ type: "error" }, ({ message }) => message)
30
+ .with({ type: "loading" }, () => "Loading...")
31
+ .exhaustive();
32
+ }
33
+ ```
34
+
35
+ Use patterns to make nested branching readable:
36
+
37
+ ```ts
38
+ import { match, P } from "ts-pattern";
39
+
40
+ const canEdit = match(user)
41
+ .with({ role: "admin" }, () => true)
42
+ .with({ role: "member", permissions: P.array("write") }, () => true)
43
+ .otherwise(() => false);
44
+ ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: usehooks
3
+ description: Use @uidotdev/usehooks for common React hooks in this project. Use when adding client-side hooks for browser state, media queries, local storage, timers, events, clipboard, network state, or DOM measurements.
4
+ ---
5
+
6
+ # useHooks
7
+
8
+ ## Instructions
9
+
10
+ - Import hooks from `@uidotdev/usehooks`.
11
+ - Use these hooks only from Client Components or other client-only hooks. Add `"use client"` where the component uses browser APIs.
12
+ - Prefer existing useHooks utilities before writing one-off hooks for common browser behavior.
13
+ - Keep server data fetching out of these hooks. Use framework data-fetching patterns for server data and useHooks for client/browser state.
14
+ - Check SSR behavior before use. For browser-only values, handle the initial render state deliberately.
15
+
16
+ ## Common Patterns
17
+
18
+ ```tsx
19
+ "use client";
20
+
21
+ import { useMediaQuery } from "@uidotdev/usehooks";
22
+
23
+ export function ResponsiveSidebar() {
24
+ const isDesktop = useMediaQuery("(min-width: 1024px)");
25
+ return isDesktop ? <aside /> : null;
26
+ }
27
+ ```
28
+
29
+ ```tsx
30
+ "use client";
31
+
32
+ import { useLocalStorage } from "@uidotdev/usehooks";
33
+
34
+ export function ThemeToggle() {
35
+ const [theme, setTheme] = useLocalStorage("theme", "system");
36
+ return <button onClick={() => setTheme("dark")}>{theme}</button>;
37
+ }
38
+ ```