diff --git a/packages/start/README.md b/packages/start/README.md index 591a7aa6e..2910444f7 100644 --- a/packages/start/README.md +++ b/packages/start/README.md @@ -13,6 +13,7 @@ A set of primitives for Solid Start - [`createServerCookie`](#createservercookie) - Provides a getter and setter for a reactive cookie, which works isomorphically. - [`createUserTheme`](#createusertheme) - Creates a Server Cookie providing a type safe way to store a theme and access it on the server or client. +- [`useUserAgent`](#useuseragent) - Creates a Server Cookie providing a type safe way to store a theme and access it on the server or client. ## Installation @@ -66,6 +67,14 @@ const [theme, setTheme] = createUserTheme("cookieName", { theme(); // => "light" | "dark" ``` +## `useUserAgent` + +Provides the value of the `userAgent` string isomorphically on the client or server + +```ts +const userAgent: string | null = useUserAgent(); +``` + ## Demo You can view a demo of this primitive here: diff --git a/packages/start/dev/index.tsx b/packages/start/dev/index.tsx index 340378c0f..210f531b0 100644 --- a/packages/start/dev/index.tsx +++ b/packages/start/dev/index.tsx @@ -1,8 +1,8 @@ -import { Component, createSignal } from "solid-js"; - +import { Component } from "solid-js"; +import { createUserTheme } from "../"; const App: Component = () => { - const [count, setCount] = createSignal(0); - const increment = () => setCount(count() + 1); + const [theme, setTheme] = createUserTheme(); + const increment = () => setTheme(theme() === "light" ? "dark" : "light"); return (
@@ -10,7 +10,7 @@ const App: Component = () => {

Counter component

it's very important...

diff --git a/packages/start/package.json b/packages/start/package.json index 6325f5ed9..31cfc5c10 100644 --- a/packages/start/package.json +++ b/packages/start/package.json @@ -20,7 +20,8 @@ "stage": 0, "list": [ "createServerCookie", - "createUserTheme" + "createUserTheme", + "useUserAgent" ], "category": "Solid Start" }, diff --git a/packages/start/src/index.ts b/packages/start/src/index.ts index 259879c0e..55469f9d6 100644 --- a/packages/start/src/index.ts +++ b/packages/start/src/index.ts @@ -1,101 +1,3 @@ -import { createSignal, createEffect, Signal } from "solid-js"; -import { isServer } from "solid-js/web"; -import { parseCookie } from "solid-start"; -import { useRequest } from "solid-start/server"; - -export type MaxAgeOptions = { - /** - * The maximum age of the cookie in seconds. Defaults to 1 year. - */ - cookieMaxAge?: number; -}; - -export type ServerCookieOptions = MaxAgeOptions & { - /** - * A function to deserialize the cookie value to be used as signal value - */ - deserialize?: (str: string | undefined) => T; - /** - * A function to serialize the signal value to be used as cookie value - */ - serialize?: (value: T) => string; -}; - -const YEAR = 365 * 24 * 60 * 60; - -/** - * A primitive for creating a cookie that can be accessed isomorphically on the client, or the server - * - * @param name The name of the cookie to be set - * @param options Options for the cookie {@see ServerCookieOptions} - * @return Returns an accessor and setter to manage the user's current theme - */ -export function createServerCookie( - name: string, - options: ServerCookieOptions & { - deserialize: (str: string | undefined) => T; - serialize: (value: T) => string; - }, -): Signal; -export function createServerCookie( - name: string, - options?: ServerCookieOptions, -): Signal; -export function createServerCookie( - name: string, - options?: ServerCookieOptions, -): Signal { - const { - deserialize = (v: any) => v as T, - serialize = String, - cookieMaxAge = YEAR, - } = options ?? {}; - - const [cookie, setCookie] = createSignal( - deserialize( - parseCookie(isServer ? useRequest().request.headers.get("cookie") ?? "" : document.cookie)[ - name - ], - ), - ); - - createEffect(p => { - const string = serialize(cookie()); - if (p !== string) document.cookie = `${name}=${string};max-age=${cookieMaxAge}`; - return string; - }); - - return [cookie, setCookie]; -} - -export type Theme = "light" | "dark"; - -export type UserThemeOptions = MaxAgeOptions & { - /** - * The default theme to be used if the cookie is not set - */ - defaultValue?: Theme; -}; - -/** - * Composes {@link createServerCookie} to provide a type safe way to store a theme and access it on the server or client. - * - * @param name The name of the cookie to be set - * @param options Options for the cookie {@link UserThemeOptions} - */ -export function createUserTheme( - name: string | undefined, - options: UserThemeOptions & { defaultValue: Theme }, -): Signal; -export function createUserTheme( - name?: string, - options?: UserThemeOptions, -): Signal; -export function createUserTheme(name = "theme", options?: UserThemeOptions): Signal { - const defaultValue = options?.defaultValue; - return createServerCookie(name, { - ...options, - deserialize: str => (str === "light" || str === "dark" ? str : defaultValue), - serialize: String, - }); -} +export * from "./serverCookie"; +export * from "./userTheme"; +export * from "./userAgent"; diff --git a/packages/start/src/serverCookie.ts b/packages/start/src/serverCookie.ts new file mode 100644 index 000000000..598b66fd9 --- /dev/null +++ b/packages/start/src/serverCookie.ts @@ -0,0 +1,69 @@ +import { createSignal, createEffect, Signal } from "solid-js"; +import { isServer } from "solid-js/web"; +import { parseCookie } from "solid-start"; +import { useRequest } from "solid-start/server"; + +export type MaxAgeOptions = { + /** + * The maximum age of the cookie in seconds. Defaults to 1 year. + */ + cookieMaxAge?: number; +}; + +export type ServerCookieOptions = MaxAgeOptions & { + /** + * A function to deserialize the cookie value to be used as signal value + */ + deserialize?: (str: string | undefined) => T; + /** + * A function to serialize the signal value to be used as cookie value + */ + serialize?: (value: T) => string; +}; + +const YEAR = 365 * 24 * 60 * 60; + +/** + * A primitive for creating a cookie that can be accessed isomorphically on the client, or the server + * + * @param name The name of the cookie to be set + * @param options Options for the cookie {@see ServerCookieOptions} + * @return Returns an accessor and setter to manage the user's current theme + */ +export function createServerCookie( + name: string, + options: ServerCookieOptions & { + deserialize: (str: string | undefined) => T; + serialize: (value: T) => string; + }, +): Signal; +export function createServerCookie( + name: string, + options?: ServerCookieOptions, +): Signal; +export function createServerCookie( + name: string, + options?: ServerCookieOptions, +): Signal { + const { + deserialize = (v: any) => v as T, + serialize = String, + cookieMaxAge = YEAR, + } = options ?? {}; + + const [cookie, setCookie] = createSignal( + deserialize( + parseCookie(isServer ? useRequest().request.headers.get("cookie") ?? "" : document.cookie)[ + name + ], + ), + ); + + createEffect(p => { + const string = serialize(cookie()); + if (p !== string) document.cookie = `${name}=${string};max-age=${cookieMaxAge}`; + return string; + }); + + return [cookie, setCookie]; +} \ No newline at end of file diff --git a/packages/start/src/userAgent.ts b/packages/start/src/userAgent.ts new file mode 100644 index 000000000..ab2b96908 --- /dev/null +++ b/packages/start/src/userAgent.ts @@ -0,0 +1,13 @@ +import { useRequest } from "solid-start/server"; +import { isServer } from "solid-js/web"; +/** A primitive that allows for the user agent string to be accessed isomorphically on the client, or on the server + * @return Returns the user agent string, or null + */ +export function useUserAgent() { + const event = useRequest(); + if (isServer) { + const userAgent = event.request.headers.get("user-agent") ?? null; + return userAgent; + } + return navigator.userAgent; +} diff --git a/packages/start/src/userTheme.ts b/packages/start/src/userTheme.ts new file mode 100644 index 000000000..a650499ff --- /dev/null +++ b/packages/start/src/userTheme.ts @@ -0,0 +1,34 @@ +import { Signal } from "solid-js"; +import { MaxAgeOptions, createServerCookie } from "./serverCookie"; + +export type Theme = "light" | "dark"; + +export type UserThemeOptions = MaxAgeOptions & { + /** + * The default theme to be used if the cookie is not set + */ + defaultValue?: Theme; +}; + +/** + * Composes {@link createServerCookie} to provide a type safe way to store a theme and access it on the server or client. + * + * @param name The name of the cookie to be set + * @param options Options for the cookie {@link UserThemeOptions} + */ +export function createUserTheme( + name: string | undefined, + options: UserThemeOptions & { defaultValue: Theme }, +): Signal; +export function createUserTheme( + name?: string, + options?: UserThemeOptions, +): Signal; +export function createUserTheme(name = "theme", options?: UserThemeOptions): Signal { + const defaultValue = options?.defaultValue; + return createServerCookie(name, { + ...options, + deserialize: str => (str === "light" || str === "dark" ? str : defaultValue), + serialize: String, + }); +}