Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Change state injection to be fully Web-spec compliant #148

Merged
merged 4 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/healthy-papayas-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@inox-tools/request-state': patch
---

Refactor client state injection to be fully compliant with Web APIs
10 changes: 10 additions & 0 deletions .github/workflows/example-deploys.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
- sitemap-ext
- custom-routing
- astro-when
- request-state
- request-nanostores
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -73,6 +75,14 @@ jobs:
- name: Discard all git changes
run: git restore .

- name: Create Cloudflare Pages Project
run: |-
pnpm dlx wrangler pages project create 'inox-tools-ex-${{ matrix.example }}' --production-branch main || true
working-directory: examples/${{ matrix.example }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

- name: Publish to Cloudflare Pages
run: pnpm dlx wrangler pages deploy dist --project-name 'inox-tools-ex-${{ matrix.example }}' --branch '${{ github.head_ref }}'
working-directory: examples/${{ matrix.example }}
Expand Down
5 changes: 4 additions & 1 deletion examples/deploy-cloudflare.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ if [ ! -d "$SCRIPT_DIR/$PROJECT_NAME" ]; then
exit 1
fi

cp "$SCRIPT_DIR/wrangler.toml" "$SCRIPT_DIR/$PROJECT_NAME/wrangler.toml"
echo "name = \"inox-tools-ex-$PROJECT_NAME\"" >>"$SCRIPT_DIR/$PROJECT_NAME/wrangler.toml"

cd "$SCRIPT_DIR/$PROJECT_NAME"

# Check that the project is an Astro project
Expand All @@ -29,7 +32,7 @@ fi
pnpm astro add cloudflare --yes

# Build the project
pnpm build
pnpm astro build

# Restore the Astro project to its original state
git restore package.json astro.config.*
24 changes: 24 additions & 0 deletions examples/request-nanostores/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# build output
dist/
# generated types
.astro/

# dependencies
node_modules/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*


# environment variables
.env
.env.production

# macOS-specific files
.DS_Store

# jetbrains setting folder
.idea/
11 changes: 11 additions & 0 deletions examples/request-nanostores/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Astro Example: Nanostores

```sh
npm create astro@latest -- --template with-nanostores
```

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/with-nanostores)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/with-nanostores)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/with-nanostores/devcontainer.json)

This example showcases using [`nanostores`](https://github.com/nanostores/nanostores) to provide shared state between components of any framework. [**Read our documentation on sharing state**](https://docs.astro.build/en/core-concepts/sharing-state/) for a complete breakdown of this project, along with guides to use React, Vue, Svelte, or Solid!
19 changes: 19 additions & 0 deletions examples/request-nanostores/astro.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
import requestNanostores from '@inox-tools/request-nanostores';

import node from '@astrojs/node';

// https://astro.build/config
export default defineConfig({
// Enable many frameworks to support all different kinds of components.
integrations: [preact(), requestNanostores()],
compressHTML: false,
output: 'server',
adapter: node({
mode: 'standalone',
}),
experimental: {
actions: true,
},
});
24 changes: 24 additions & 0 deletions examples/request-nanostores/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@example/request-nanostores",
"private": true,
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/check": "^0.9.3",
"@astrojs/node": "^8.3.3",
"@astrojs/preact": "^3.5.1",
"@inox-tools/request-nanostores": "workspace:^",
"@nanostores/preact": "^0.5.2",
"astro": "^4.14.2",
"nanostores": "^0.11.2",
"preact": "^10.23.1",
"typescript": "^5.5.4"
}
}
9 changes: 9 additions & 0 deletions examples/request-nanostores/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions examples/request-nanostores/src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { defineAction, z } from 'astro:actions';
import { CART_COOKIE_NAME, cookieOptions, extractCartCookie } from '../cookies';

export const server = {
setCartItem: defineAction({
input: z.object({
id: z.string(),
quantity: z.number(),
}),
handler: async (input, ctx) => {
const currentCookie = extractCartCookie(ctx.cookies);

const newCookie = {
...currentCookie,
[input.id]: {
id: input.id,
quantity: input.quantity,
},
};

ctx.cookies.set(CART_COOKIE_NAME, newCookie, cookieOptions(ctx.url));
},
}),
};
40 changes: 40 additions & 0 deletions examples/request-nanostores/src/cartStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { shared } from '@it-astro:request-nanostores';
import { atom, map, task } from 'nanostores';
import { actions } from 'astro:actions';

export const isCartOpen = shared('isCartOpen', atom(false));

export type CartItem = {
id: string;
name: string;
imageSrc: string;
quantity: number;
};

export type CartItemDisplayInfo = Pick<CartItem, 'id' | 'name' | 'imageSrc'>;

export const cartItems = shared('cartItems', map<Record<string, CartItem>>({}));

export function addCartItem({ id, name, imageSrc }: CartItemDisplayInfo) {
task(async () => {
const existingEntry = cartItems.get()[id];
const newEntry = existingEntry
? {
...existingEntry,
quantity: existingEntry.quantity + 1,
}
: {
id,
name,
imageSrc,
quantity: 1,
};

cartItems.setKey(id, newEntry);

await actions.setCartItem({
id: newEntry.id,
quantity: newEntry.quantity,
});
});
}
18 changes: 18 additions & 0 deletions examples/request-nanostores/src/components/AddToCartForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { isCartOpen, addCartItem } from '../cartStore';
import type { CartItemDisplayInfo } from '../cartStore';
import type { ComponentChildren } from 'preact';

type Props = {
item: CartItemDisplayInfo;
children: ComponentChildren;
};

export default function AddToCartForm({ item, children }: Props) {
async function addToCart(e: SubmitEvent) {
e.preventDefault();
isCartOpen.set(true);
await addCartItem(item);
}

return <form onSubmit={addToCart}>{children}</form>;
}
29 changes: 29 additions & 0 deletions examples/request-nanostores/src/components/CartFlyout.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.container {
position: fixed;
right: 0;
top: var(--nav-height);
height: 100vh;
background: var(--color-bg-2);
padding-inline: 2rem;
min-width: min(90vw, 300px);
border-left: 3px solid var(--color-bg-3);
}

.list {
list-style: none;
padding: 0;
}

.listItem {
display: flex;
gap: 1rem;
align-items: center;
}

.listItem * {
margin-block: 0.3rem;
}

.listItemImg {
width: 4rem;
}
28 changes: 28 additions & 0 deletions examples/request-nanostores/src/components/CartFlyout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useStore } from '@nanostores/preact';
import { cartItems, isCartOpen } from '../cartStore';
import styles from './CartFlyout.module.css';

export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
const $cartItems = useStore(cartItems);

return (
<aside hidden={!$isCartOpen} className={styles.container}>
{Object.values($cartItems).length ? (
<ul className={styles.list} role="list">
{Object.values($cartItems).map((cartItem) => (
<li className={styles.listItem}>
<img className={styles.listItemImg} src={cartItem.imageSrc} alt={cartItem.name} />
<div>
<h3>{cartItem.name}</h3>
<p>Quantity: {cartItem.quantity}</p>
</div>
</li>
))}
</ul>
) : (
<p>Your cart is empty!</p>
)}
</aside>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useStore } from '@nanostores/preact';
import { isCartOpen } from '../cartStore';

export default function CartFlyoutToggle() {
const $isCartOpen = useStore(isCartOpen);
return <button onClick={() => isCartOpen.set(!$isCartOpen)}>Cart</button>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<h1>Astronaut Figurine</h1>
<p class="limited-edition-badge">Limited Edition</p>
<p>
The limited edition Astronaut Figurine is the perfect gift for any Astro contributor. This
fully-poseable action figurine comes equipped with:
</p>
<ul>
<li>A fabric space suit with adjustable straps</li>
<li>Boots lightly dusted by the lunar surface *</li>
<li>An adjustable space visor</li>
</ul>
<p>
<sub>* Dust not actually from the lunar surface</sub>
</p>

<style>
h1 {
margin: 0;
margin-block-start: 2rem;
}

.limited-edition-badge {
font-weight: 700;
text-transform: uppercase;
background-image: linear-gradient(0deg, var(--astro-blue), var(--astro-pink));
background-size: 100% 200%;
background-position-y: 100%;
border-radius: 0.4rem;
animation: pulse 4s ease-in-out infinite;
display: inline-block;
color: white;
padding: 0.2rem 0.4rem;
}

@keyframes pulse {
0%,
100% {
background-position-y: 0%;
}
50% {
background-position-y: 80%;
}
}
</style>
28 changes: 28 additions & 0 deletions examples/request-nanostores/src/cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { AstroCookies, AstroCookieSetOptions } from 'astro';
import type { CartItem } from './cartStore';

type CartCookie = Record<string, Pick<CartItem, 'id' | 'quantity'>>;

export const CART_COOKIE_NAME = 'cart';

export function cookieOptions(url: URL): AstroCookieSetOptions {
return {
path: '/',
domain: url.hostname,
httpOnly: true,
secure: url.protocol === 'https:',
sameSite: 'lax',
maxAge: 3600,
};
}

export function extractCartCookie(cookies: AstroCookies): CartCookie {
const cookie = cookies.get(CART_COOKIE_NAME);
if (!cookie) return {};

try {
return cookie.json();
} catch {
return {};
}
}
1 change: 1 addition & 0 deletions examples/request-nanostores/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference path="../.astro/types.d.ts" />
Loading
Loading