Quellcode durchsuchen

add solid example (#208)

Chris Tate vor 4 Monaten
Ursprung
Commit
316439fcd7

+ 12 - 0
examples/solid/index.html

@@ -0,0 +1,12 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>json-render solid example</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/index.tsx"></script>
+  </body>
+</html>

+ 23 - 0
examples/solid/package.json

@@ -0,0 +1,23 @@
+{
+  "name": "example-solid",
+  "version": "0.1.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "portless solid-demo.json-render vite",
+    "build": "vite build",
+    "preview": "vite preview",
+    "check-types": "tsc --noEmit"
+  },
+  "dependencies": {
+    "@json-render/core": "workspace:*",
+    "@json-render/solid": "workspace:*",
+    "solid-js": "^1.9.11",
+    "zod": "^4.3.6"
+  },
+  "devDependencies": {
+    "typescript": "^5.9.3",
+    "vite": "^7.3.1",
+    "vite-plugin-solid": "^2.11.10"
+  }
+}

+ 13 - 0
examples/solid/src/App.tsx

@@ -0,0 +1,13 @@
+import { StateProvider } from "@json-render/solid";
+import { demoSpec } from "./lib/spec";
+import { DemoRenderer } from "./DemoRenderer";
+
+export function App() {
+  const initialState = demoSpec.state ?? {};
+
+  return (
+    <StateProvider initialState={initialState}>
+      <DemoRenderer />
+    </StateProvider>
+  );
+}

+ 51 - 0
examples/solid/src/DemoRenderer.tsx

@@ -0,0 +1,51 @@
+import {
+  ActionProvider,
+  ValidationProvider,
+  VisibilityProvider,
+  Renderer,
+  useStateStore,
+} from "@json-render/solid";
+import { demoSpec } from "./lib/spec";
+import { registry } from "./lib/registry";
+
+export function DemoRenderer() {
+  const stateStore = useStateStore();
+
+  const handlers = {
+    increment: async () => {
+      stateStore.set("/count", Number(stateStore.get("/count") || 0) + 1);
+    },
+    decrement: async () => {
+      stateStore.set(
+        "/count",
+        Math.max(0, Number(stateStore.get("/count") || 0) - 1),
+      );
+    },
+    reset: async () => {
+      stateStore.set("/count", 0);
+    },
+    toggleItem: async (params: Record<string, unknown>) => {
+      const index = params.index as number;
+      const todos = (
+        stateStore.get("/todos") as Array<{
+          id: number;
+          title: string;
+          completed: boolean;
+        }>
+      ).map((item, i) =>
+        i === index ? { ...item, completed: !item.completed } : item,
+      );
+      stateStore.set("/todos", todos);
+    },
+  };
+
+  return (
+    <ActionProvider handlers={handlers}>
+      <VisibilityProvider>
+        <ValidationProvider>
+          <Renderer spec={demoSpec} registry={registry} />
+        </ValidationProvider>
+      </VisibilityProvider>
+    </ActionProvider>
+  );
+}

+ 13 - 0
examples/solid/src/app.css

@@ -0,0 +1,13 @@
+* {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+}
+
+body {
+  font-family:
+    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+  background: #f9fafb;
+  color: #111827;
+  min-height: 100vh;
+}

+ 5 - 0
examples/solid/src/index.tsx

@@ -0,0 +1,5 @@
+import { render } from "solid-js/web";
+import { App } from "./App";
+import "./app.css";
+
+render(() => <App />, document.getElementById("app")!);

+ 90 - 0
examples/solid/src/lib/catalog.ts

@@ -0,0 +1,90 @@
+import { schema } from "@json-render/solid/schema";
+import { z } from "zod";
+
+export const catalog = schema.createCatalog({
+  components: {
+    Stack: {
+      props: z.object({
+        gap: z.number().optional(),
+        padding: z.number().optional(),
+        direction: z.enum(["vertical", "horizontal"]).optional(),
+        align: z.enum(["start", "center", "end"]).optional(),
+      }),
+      slots: ["default"],
+      description:
+        "Layout container that stacks children vertically or horizontally",
+    },
+    Card: {
+      props: z.object({
+        title: z.string().optional(),
+        subtitle: z.string().optional(),
+      }),
+      slots: ["default"],
+      description: "A card container with optional title and subtitle",
+    },
+    Text: {
+      props: z.object({
+        content: z.string(),
+        size: z.enum(["sm", "md", "lg", "xl"]).optional(),
+        weight: z.enum(["normal", "medium", "bold"]).optional(),
+        color: z.string().optional(),
+      }),
+      slots: [],
+      description: "Displays a text string",
+    },
+    Button: {
+      props: z.object({
+        label: z.string(),
+        variant: z.enum(["primary", "secondary", "danger"]).optional(),
+        disabled: z.boolean().optional(),
+      }),
+      slots: [],
+      description: "A clickable button that emits a 'press' event",
+    },
+    Badge: {
+      props: z.object({
+        label: z.string(),
+        color: z.string().optional(),
+      }),
+      slots: [],
+      description: "A small badge/tag label",
+    },
+    ListItem: {
+      props: z.object({
+        title: z.string(),
+        description: z.string().optional(),
+        completed: z.boolean().optional(),
+      }),
+      slots: [],
+      description: "A single item in a list",
+    },
+    Input: {
+      props: z.object({
+        value: z.string().optional(),
+        placeholder: z.string().optional(),
+      }),
+      slots: [],
+      description: "A text input field that supports two-way state binding",
+    },
+  },
+  actions: {
+    increment: {
+      params: z.object({}),
+      description: "Increment the counter by 1",
+    },
+    decrement: {
+      params: z.object({}),
+      description: "Decrement the counter by 1",
+    },
+    reset: {
+      params: z.object({}),
+      description: "Reset the counter to 0",
+    },
+    toggleItem: {
+      params: z.object({
+        index: z.number(),
+      }),
+      description: "Toggle the completed state of a todo item",
+    },
+  },
+});

+ 27 - 0
examples/solid/src/lib/components/Badge.tsx

@@ -0,0 +1,27 @@
+import type { BaseComponentProps } from "@json-render/solid";
+
+interface BadgeProps {
+  label: string;
+  color?: string;
+}
+
+export function Badge(renderProps: BaseComponentProps<BadgeProps>) {
+  const color = () => renderProps.props.color ?? "#6b7280";
+
+  return (
+    <span
+      style={{
+        display: "inline-block",
+        padding: "4px 12px",
+        "border-radius": "9999px",
+        "font-size": "13px",
+        "font-weight": "500",
+        "background-color": `${color()}20`,
+        color: color(),
+        border: `1px solid ${color()}40`,
+      }}
+    >
+      {renderProps.props.label}
+    </span>
+  );
+}

+ 47 - 0
examples/solid/src/lib/components/Button.tsx

@@ -0,0 +1,47 @@
+import type { BaseComponentProps } from "@json-render/solid";
+
+interface ButtonProps {
+  label: string;
+  variant?: "primary" | "secondary" | "danger";
+  disabled?: boolean;
+}
+
+const variantStyles: Record<string, Record<string, string>> = {
+  primary: {
+    background: "#2563eb",
+    color: "#ffffff",
+    border: "1px solid #2563eb",
+  },
+  secondary: {
+    background: "#f3f4f6",
+    color: "#374151",
+    border: "1px solid #d1d5db",
+  },
+  danger: {
+    background: "#fee2e2",
+    color: "#dc2626",
+    border: "1px solid #fca5a5",
+  },
+};
+
+export function Button(renderProps: BaseComponentProps<ButtonProps>) {
+  const variant = () => renderProps.props.variant ?? "primary";
+
+  return (
+    <button
+      disabled={renderProps.props.disabled}
+      onClick={() => renderProps.emit("press")}
+      style={{
+        padding: "8px 16px",
+        "border-radius": "8px",
+        "font-size": "14px",
+        "font-weight": "500",
+        cursor: renderProps.props.disabled ? "not-allowed" : "pointer",
+        opacity: renderProps.props.disabled ? "0.5" : "1",
+        ...variantStyles[variant()],
+      }}
+    >
+      {renderProps.props.label}
+    </button>
+  );
+}

+ 44 - 0
examples/solid/src/lib/components/Card.tsx

@@ -0,0 +1,44 @@
+import type { BaseComponentProps } from "@json-render/solid";
+
+interface CardProps {
+  title?: string;
+  subtitle?: string;
+}
+
+export function Card(renderProps: BaseComponentProps<CardProps>) {
+  return (
+    <div
+      style={{
+        background: "#ffffff",
+        "border-radius": "12px",
+        border: "1px solid #e5e7eb",
+        padding: "20px",
+        "box-shadow": "0 1px 3px rgba(0,0,0,0.06)",
+      }}
+    >
+      {renderProps.props.title && (
+        <h2
+          style={{
+            "font-size": "18px",
+            "font-weight": "600",
+            "margin-bottom": renderProps.props.subtitle ? "4px" : "16px",
+          }}
+        >
+          {renderProps.props.title}
+        </h2>
+      )}
+      {renderProps.props.subtitle && (
+        <p
+          style={{
+            "font-size": "14px",
+            color: "#6b7280",
+            "margin-bottom": "16px",
+          }}
+        >
+          {renderProps.props.subtitle}
+        </p>
+      )}
+      {renderProps.children}
+    </div>
+  );
+}

+ 30 - 0
examples/solid/src/lib/components/Input.tsx

@@ -0,0 +1,30 @@
+import type { BaseComponentProps } from "@json-render/solid";
+import { useBoundProp } from "@json-render/solid";
+
+interface InputProps {
+  value?: string;
+  placeholder?: string;
+}
+
+export function Input(renderProps: BaseComponentProps<InputProps>) {
+  const [value, setValue] = useBoundProp<string>(
+    renderProps.props.value,
+    renderProps.bindings?.value,
+  );
+
+  return (
+    <input
+      value={String(value ?? "")}
+      onInput={(e) => setValue(e.currentTarget.value)}
+      placeholder={renderProps.props.placeholder ?? ""}
+      style={{
+        padding: "8px 12px",
+        "border-radius": "8px",
+        border: "1px solid #d1d5db",
+        "font-size": "14px",
+        outline: "none",
+        width: "100%",
+      }}
+    />
+  );
+}

+ 55 - 0
examples/solid/src/lib/components/ListItem.tsx

@@ -0,0 +1,55 @@
+import type { BaseComponentProps } from "@json-render/solid";
+
+interface ListItemProps {
+  title: string;
+  description?: string;
+  completed?: boolean;
+}
+
+export function ListItem(renderProps: BaseComponentProps<ListItemProps>) {
+  return (
+    <div
+      onClick={() => renderProps.emit("press")}
+      style={{
+        display: "flex",
+        "align-items": "center",
+        gap: "10px",
+        padding: "10px 12px",
+        "border-radius": "8px",
+        border: "1px solid #e5e7eb",
+        cursor: "pointer",
+        "user-select": "none",
+        background: renderProps.props.completed ? "#f0fdf4" : "#ffffff",
+      }}
+    >
+      <span
+        style={{
+          width: "22px",
+          height: "22px",
+          "border-radius": "6px",
+          border: renderProps.props.completed
+            ? "2px solid #22c55e"
+            : "2px solid #d1d5db",
+          display: "flex",
+          "align-items": "center",
+          "justify-content": "center",
+          "font-size": "13px",
+          color: "#22c55e",
+          "flex-shrink": "0",
+        }}
+      >
+        {renderProps.props.completed ? "\u2713" : ""}
+      </span>
+      <span
+        style={{
+          "text-decoration": renderProps.props.completed
+            ? "line-through"
+            : "none",
+          color: renderProps.props.completed ? "#9ca3af" : "#111827",
+        }}
+      >
+        {renderProps.props.title}
+      </span>
+    </div>
+  );
+}

+ 27 - 0
examples/solid/src/lib/components/Stack.tsx

@@ -0,0 +1,27 @@
+import type { BaseComponentProps } from "@json-render/solid";
+
+interface StackProps {
+  gap?: number;
+  padding?: number;
+  direction?: "vertical" | "horizontal";
+  align?: "start" | "center" | "end";
+}
+
+export function Stack(renderProps: BaseComponentProps<StackProps>) {
+  return (
+    <div
+      style={{
+        display: "flex",
+        "flex-direction":
+          renderProps.props.direction === "horizontal" ? "row" : "column",
+        gap: renderProps.props.gap ? `${renderProps.props.gap}px` : undefined,
+        padding: renderProps.props.padding
+          ? `${renderProps.props.padding}px`
+          : undefined,
+        "align-items": renderProps.props.align,
+      }}
+    >
+      {renderProps.children}
+    </div>
+  );
+}

+ 35 - 0
examples/solid/src/lib/components/Text.tsx

@@ -0,0 +1,35 @@
+import type { BaseComponentProps } from "@json-render/solid";
+
+interface TextProps {
+  content: string;
+  size?: "sm" | "md" | "lg" | "xl";
+  weight?: "normal" | "medium" | "bold";
+  color?: string;
+}
+
+const sizeMap: Record<string, string> = {
+  sm: "12px",
+  md: "14px",
+  lg: "18px",
+  xl: "24px",
+};
+
+const weightMap: Record<string, string> = {
+  normal: "400",
+  medium: "500",
+  bold: "700",
+};
+
+export function Text(renderProps: BaseComponentProps<TextProps>) {
+  return (
+    <span
+      style={{
+        "font-size": sizeMap[renderProps.props.size ?? "md"],
+        "font-weight": weightMap[renderProps.props.weight ?? "normal"],
+        color: renderProps.props.color,
+      }}
+    >
+      {String(renderProps.props.content ?? "")}
+    </span>
+  );
+}

+ 29 - 0
examples/solid/src/lib/registry.tsx

@@ -0,0 +1,29 @@
+import { type Components, defineRegistry } from "@json-render/solid";
+import { catalog } from "./catalog";
+import { Stack } from "./components/Stack";
+import { Card } from "./components/Card";
+import { Text } from "./components/Text";
+import { Button } from "./components/Button";
+import { Badge } from "./components/Badge";
+import { ListItem } from "./components/ListItem";
+import { Input } from "./components/Input";
+
+const components: Components<typeof catalog> = {
+  Stack,
+  Card,
+  Text,
+  Button,
+  Badge,
+  ListItem,
+  Input,
+};
+
+export const { registry, handlers: makeHandlers } = defineRegistry(catalog, {
+  components,
+  actions: {
+    increment: async () => {},
+    decrement: async () => {},
+    reset: async () => {},
+    toggleItem: async () => {},
+  },
+});

+ 130 - 0
examples/solid/src/lib/spec.ts

@@ -0,0 +1,130 @@
+import type { Spec } from "@json-render/core";
+
+export const demoSpec: Spec = {
+  root: "root",
+  state: {
+    count: 0,
+    name: "",
+    todos: [
+      { id: 1, title: "Learn SolidJS", completed: true },
+      { id: 2, title: "Try @json-render/solid", completed: false },
+      { id: 3, title: "Build something awesome", completed: false },
+    ],
+  },
+  elements: {
+    root: {
+      type: "Stack",
+      props: { gap: 24, padding: 24, direction: "vertical" },
+      children: [
+        "header",
+        "counter-card",
+        "milestone-badge",
+        "todos-card",
+        "input-card",
+      ],
+    },
+    header: {
+      type: "Text",
+      props: {
+        content: "@json-render/solid demo",
+        size: "xl",
+        weight: "bold",
+      },
+    },
+    "counter-card": {
+      type: "Card",
+      props: {
+        title: "Counter",
+        subtitle: "Click the buttons to change the count",
+      },
+      children: ["counter-body"],
+    },
+    "counter-body": {
+      type: "Stack",
+      props: { gap: 12, direction: "horizontal", align: "center" },
+      children: [
+        "decrement-btn",
+        "counter-value",
+        "increment-btn",
+        "reset-btn",
+      ],
+    },
+    "decrement-btn": {
+      type: "Button",
+      props: { label: "\u2212", variant: "secondary" },
+      on: { press: { action: "decrement" } },
+    },
+    "counter-value": {
+      type: "Text",
+      props: {
+        content: { $state: "/count" },
+        size: "xl",
+        weight: "bold",
+      },
+    },
+    "increment-btn": {
+      type: "Button",
+      props: { label: "+", variant: "primary" },
+      on: { press: { action: "increment" } },
+    },
+    "reset-btn": {
+      type: "Button",
+      props: { label: "Reset", variant: "danger" },
+      on: { press: { action: "reset" } },
+    },
+    "milestone-badge": {
+      type: "Badge",
+      props: { label: "Milestone reached: 10!", color: "#10b981" },
+      visible: { $state: "/count", gte: 10 },
+    },
+    "todos-card": {
+      type: "Card",
+      props: { title: "Todo List", subtitle: "Your tasks" },
+      children: ["todos-list"],
+    },
+    "todos-list": {
+      type: "Stack",
+      props: { gap: 8, direction: "vertical" },
+      repeat: { statePath: "/todos", key: "id" },
+      children: ["todo-item"],
+    },
+    "todo-item": {
+      type: "ListItem",
+      props: {
+        title: { $item: "title" },
+        completed: { $item: "completed" },
+      },
+      on: {
+        press: { action: "toggleItem", params: { index: { $index: true } } },
+      },
+    },
+    "input-card": {
+      type: "Card",
+      props: {
+        title: "Bound Input",
+        subtitle: "Type to update state and see reactive text",
+      },
+      children: ["input-body"],
+    },
+    "input-body": {
+      type: "Stack",
+      props: { gap: 12, direction: "vertical" },
+      children: ["name-input", "name-display"],
+    },
+    "name-input": {
+      type: "Input",
+      props: {
+        value: { $bindState: "/name" },
+        placeholder: "Enter your name...",
+      },
+    },
+    "name-display": {
+      type: "Text",
+      props: {
+        content: { $state: "/name" },
+        size: "md",
+        color: "#6b7280",
+      },
+    },
+  },
+};

+ 18 - 0
examples/solid/tsconfig.json

@@ -0,0 +1,18 @@
+{
+  "compilerOptions": {
+    "target": "ES2022",
+    "module": "ESNext",
+    "lib": ["ES2022", "DOM", "DOM.Iterable"],
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+    "strict": true,
+    "skipLibCheck": true,
+    "jsx": "preserve",
+    "jsxImportSource": "solid-js"
+  },
+  "include": ["src/**/*.ts", "src/**/*.tsx"],
+  "exclude": ["node_modules"]
+}

+ 6 - 0
examples/solid/vite.config.ts

@@ -0,0 +1,6 @@
+import { defineConfig } from "vite";
+import solid from "vite-plugin-solid";
+
+export default defineConfig({
+  plugins: [solid()],
+});

+ 28 - 13
pnpm-lock.yaml

@@ -946,6 +946,31 @@ importers:
         specifier: ^5.7.2
         version: 5.9.3
 
+  examples/solid:
+    dependencies:
+      '@json-render/core':
+        specifier: workspace:*
+        version: link:../../packages/core
+      '@json-render/solid':
+        specifier: workspace:*
+        version: link:../../packages/solid
+      solid-js:
+        specifier: ^1.9.11
+        version: 1.9.11
+      zod:
+        specifier: ^4.3.6
+        version: 4.3.6
+    devDependencies:
+      typescript:
+        specifier: ^5.9.3
+        version: 5.9.3
+      vite:
+        specifier: ^7.3.1
+        version: 7.3.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
+      vite-plugin-solid:
+        specifier: ^2.11.10
+        version: 2.11.10(solid-js@1.9.11)(vite@7.3.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
+
   examples/stripe-app/api:
     dependencies:
       '@ai-sdk/gateway':
@@ -14354,7 +14379,6 @@ snapshots:
       - graphql
       - supports-color
       - utf-8-validate
-    optional: true
 
   '@expo/code-signing-certificates@0.0.6':
     dependencies:
@@ -14419,7 +14443,6 @@ snapshots:
     optionalDependencies:
       react: 19.2.4
       react-native: 0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4)
-    optional: true
 
   '@expo/env@2.0.8':
     dependencies:
@@ -14489,7 +14512,7 @@ snapshots:
       postcss: 8.4.49
       resolve-from: 5.0.0
     optionalDependencies:
-      expo: 54.0.33(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.23)(graphql@16.12.0)(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
+      expo: 54.0.33(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.23)(graphql@16.12.0)(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4))(react@19.2.4)
     transitivePeerDependencies:
       - bufferutil
       - supports-color
@@ -14570,7 +14593,7 @@ snapshots:
       '@expo/json-file': 10.0.8
       '@react-native/normalize-colors': 0.81.5
       debug: 4.4.3
-      expo: 54.0.33(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.23)(graphql@16.12.0)(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
+      expo: 54.0.33(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.23)(graphql@16.12.0)(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4))(react@19.2.4)
       resolve-from: 5.0.0
       semver: 7.7.3
       xml2js: 0.6.0
@@ -14598,7 +14621,6 @@ snapshots:
       expo-font: 14.0.11(expo@54.0.33)(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4))(react@19.2.4)
       react: 19.2.4
       react-native: 0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4)
-    optional: true
 
   '@expo/ws-tunnel@1.0.6': {}
 
@@ -19483,7 +19505,7 @@ snapshots:
       resolve-from: 5.0.0
     optionalDependencies:
       '@babel/runtime': 7.28.6
-      expo: 54.0.33(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.23)(graphql@16.12.0)(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
+      expo: 54.0.33(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.23)(graphql@16.12.0)(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4))(react@19.2.4)
     transitivePeerDependencies:
       - '@babel/core'
       - supports-color
@@ -20916,7 +20938,6 @@ snapshots:
       react-native: 0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4)
     transitivePeerDependencies:
       - supports-color
-    optional: true
 
   expo-clipboard@8.0.8(expo@54.0.33)(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
     dependencies:
@@ -20941,7 +20962,6 @@ snapshots:
       react-native: 0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4)
     transitivePeerDependencies:
       - supports-color
-    optional: true
 
   expo-file-system@19.0.21(expo@54.0.33)(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)):
     dependencies:
@@ -20952,7 +20972,6 @@ snapshots:
     dependencies:
       expo: 54.0.33(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.23)(graphql@16.12.0)(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4))(react@19.2.4)
       react-native: 0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4)
-    optional: true
 
   expo-font@14.0.11(expo@54.0.33)(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
     dependencies:
@@ -20967,7 +20986,6 @@ snapshots:
       fontfaceobserver: 2.3.0
       react: 19.2.4
       react-native: 0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4)
-    optional: true
 
   expo-keep-awake@15.0.8(expo@54.0.33)(react@19.1.0):
     dependencies:
@@ -20978,7 +20996,6 @@ snapshots:
     dependencies:
       expo: 54.0.33(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.23)(graphql@16.12.0)(react-native@0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4))(react@19.2.4)
       react: 19.2.4
-    optional: true
 
   expo-linking@8.0.11(expo@54.0.33)(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
     dependencies:
@@ -21020,7 +21037,6 @@ snapshots:
       invariant: 2.2.4
       react: 19.2.4
       react-native: 0.83.1(@babel/core@7.29.0)(@types/react@19.2.3)(react@19.2.4)
-    optional: true
 
   expo-router@6.0.23(@expo/metro-runtime@6.1.2)(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.11)(expo@54.0.33)(react-dom@19.2.4(react@19.1.0))(react-native-reanimated@4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.11.1(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
     dependencies:
@@ -21183,7 +21199,6 @@ snapshots:
       - graphql
       - supports-color
       - utf-8-validate
-    optional: true
 
   exponential-backoff@3.1.3: {}