ValyroUI English version

Начало работы: SolidStart

SSR без вспышки: entry-server.tsx пишет тему из cookie на <html>; разметка компонентов — прямо в Solid JSX (class, как в HTML)

1. Создайте приложение

Проверено с SolidStart 2.0 и solid-js 1.9 (шаблону нужен Node 24+). Скаффолдер не ставит зависимости сам.

npm create solid@latest -- my-app --solidstart --v2 --ts --template basic
cd my-app
npm install

2. Установите пакет

Из пакета нужен только CSS: valyro-ui/styles.css (плюс файл темы, если тема не из двух встроенных). Шрифты — отдельные файлы: тема vercel (по умолчанию в ThemeProvider) использует Geist, поэтому рядом со стилями импортируйте valyro-ui/fonts/geist.css и valyro-ui/fonts/geist-mono.css, иначе текст будет системным шрифтом; glass использует системные шрифты, ей файлы не нужны. npm сам положит react и react-dom в node_modules как peer-зависимости, но в бандл они не попадут, пока вы их не импортируете. Пока пакет не опубликован, установите tarball: npm install ../valyro-ui-0.1.0.tgz.

npm install valyro-ui

3. Подключите стили

Шаблонный app.css глобально стилизует main, h1, p и a и спорит с темой — импортируйте вместо него стили пакета.

import { MetaProvider, Title } from "@solidjs/meta";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import "valyro-ui/styles.css";

export default function App() {
  return (
    <Router
      root={props => (
        <MetaProvider>
          <Title>ValyroUI on SolidStart</Title>
          <Suspense>{props.children}</Suspense>
        </MetaProvider>
      )}
    >
      <FileRoutes />
    </Router>
  );
}

4. Тема без вспышки

data-theme (vercel, glass или подключённая отдельным файлом тема) и data-mode (light или dark) на <html> включают тему: от них зависят токены --ui-* и фон страницы. Документ рендерится только на сервере: значения берутся из cookie запроса (только известные), гидрация <html> не затрагивает.

// @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server";
import { getRequestEvent } from "solid-js/web";

// Cookie values are user input: only known values reach the HTML.
function cookie(name: string, allowed: string[], fallback: string) {
  const header = getRequestEvent()?.request.headers.get("cookie") ?? "";
  const value = header
    .split(";")
    .map(part => part.trim().split("="))
    .find(([key]) => key === name)?.[1];
  return value && allowed.includes(value) ? value : fallback;
}

export default createHandler(() => (
  <StartServer
    document={({ assets, children, scripts }) => (
      <html
        lang="en"
        data-theme={cookie("ui-theme", ["glass", "vercel"], "glass")}
        data-mode={cookie("ui-mode", ["light", "dark"], "light")}
      >
        <head>
          <meta charset="utf-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <link rel="icon" href="/favicon.ico" />
          {assets}
        </head>
        <body>
          <div id="app">{children}</div>
          {scripts}
        </body>
      </html>
    )}
  />
));

5. Переключатель и разметка

Разметка — ровно то, что рендерят React-компоненты (Button, Badge, TextInput, Alert, Card). Без React работают только визуальные компоненты (список — в гайде HTML + CSS); разметку любого из них показывает пример «Использование» на странице компонента, когда выбран этот фреймворк. В Solid JSX она вставляется почти как есть: уберите только focusable="false" у <svg> — этого атрибута нет в типах Solid, и tsc его не пропустит.

import { createSignal, onMount } from "solid-js";

function save(key: "theme" | "mode", value: string) {
  document.documentElement.dataset[key] = value;
  document.cookie = `ui-${key}=${value}; path=/; max-age=31536000; samesite=lax`;
}

export default function Home() {
  // The server already wrote the cookie values on <html> (entry-server.tsx); pick them up after hydration.
  const [theme, setTheme] = createSignal("glass");
  const [mode, setMode] = createSignal("light");
  onMount(() => {
    setTheme(document.documentElement.dataset.theme ?? "glass");
    setMode(document.documentElement.dataset.mode ?? "light");
  });

  return (
    <main style={{ display: "grid", "justify-items": "start", gap: "16px", "max-width": "560px", padding: "32px" }}>
      <div style={{ display: "flex", gap: "8px" }}>
        <button class="ui-button" data-variant="secondary" data-size="md" type="button" onClick={() => save("theme", setTheme(theme() === "glass" ? "vercel" : "glass"))}>
          <span class="ui-button__label">Toggle theme</span>
        </button>
        <button class="ui-button" data-variant="secondary" data-size="md" type="button" onClick={() => save("mode", setMode(mode() === "light" ? "dark" : "light"))}>
          <span class="ui-button__label">Toggle mode</span>
        </button>
      </div>

      <button class="ui-button" data-variant="primary" data-size="md" type="button"><span class="ui-button__label">Deploy</span></button>
      <span class="ui-badge" data-color="green" data-variant="subtle" data-size="md"><span class="ui-badge__label">Ready</span></span>
      <div class="ui-field ui-text-input" data-size="md"><input aria-label="Email" placeholder="[email protected]" type="text" class="ui-field__input ui-text-input__input" /></div>
      <div role="status" class="ui-alert" data-variant="info">
        <span class="ui-alert__icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="ui-icon"><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"></path></svg></span>
        <div class="ui-alert__content"><div class="ui-alert__title">Heads up</div><div class="ui-alert__description">Static markup, no React at runtime.</div></div>
      </div>
      <div class="ui-card ui-panel" data-variant="default" data-padding="md">
        <div class="ui-card__header"><h3 class="ui-card__title">Project</h3><p class="ui-card__description">Production deployment</p></div>
        <div class="ui-card__content">Body</div>
        <div class="ui-card__footer"><button class="ui-button" data-variant="secondary" data-size="md" type="button"><span class="ui-button__label">Open</span></button></div>
      </div>
    </main>
  );
}

6. Соберите и запустите

В production консоль чистая. В npm run dev SolidStart 2.0.6 сам по себе (и на чистом шаблоне) пишет в консоль SyntaxError … resolve-uri.umd.js does not provide an export named 'default'; лечится строкой optimizeDeps: { include: ["@solidjs/start > @jridgewell/trace-mapping > @jridgewell/resolve-uri"] } в vite.config.ts.

npm run build
npm start

Другие фреймворки