Skip to content

Commit

Permalink
feat: Change state injection to be fully Web-spec compliant
Browse files Browse the repository at this point in the history
  • Loading branch information
Fryuni committed Aug 19, 2024
1 parent 8fe2620 commit 70b7b24
Show file tree
Hide file tree
Showing 24 changed files with 542 additions and 16 deletions.
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

0 comments on commit 70b7b24

Please sign in to comment.