@ssejal8/recipe-ui-kit 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -277
- package/dist/cjs/index-BEr8BsSA.js +1712 -0
- package/dist/cjs/index-BEr8BsSA.js.map +1 -0
- package/dist/cjs/index.cjs.js +5 -0
- package/dist/cjs/index.cjs.js.map +1 -0
- package/dist/cjs/loader.cjs.js +15 -0
- package/dist/cjs/loader.cjs.js.map +1 -0
- package/dist/cjs/recipe-kit.cjs.js +27 -0
- package/dist/cjs/recipe-kit.cjs.js.map +1 -0
- package/dist/cjs/rk-badge_10.cjs.entry.js +638 -0
- package/dist/cjs/rk-badge_10.cjs.entry.js.map +1 -0
- package/dist/collection/collection-manifest.json +22 -0
- package/dist/collection/components/rk-badge/rk-badge.js +97 -0
- package/dist/collection/components/rk-badge/rk-badge.js.map +1 -0
- package/dist/collection/components/rk-chip/rk-chip.js +245 -0
- package/dist/collection/components/rk-chip/rk-chip.js.map +1 -0
- package/dist/collection/components/rk-empty-state/rk-empty-state.js +138 -0
- package/dist/collection/components/rk-empty-state/rk-empty-state.js.map +1 -0
- package/dist/collection/components/rk-meal-slot/rk-meal-slot.js +312 -0
- package/dist/collection/components/rk-meal-slot/rk-meal-slot.js.map +1 -0
- package/dist/collection/components/rk-modal/rk-modal.js +256 -0
- package/dist/collection/components/rk-modal/rk-modal.js.map +1 -0
- package/dist/collection/components/rk-rating/rk-rating.js +228 -0
- package/dist/collection/components/rk-rating/rk-rating.js.map +1 -0
- package/dist/collection/components/rk-recipe-card/rk-recipe-card.js +366 -0
- package/dist/collection/components/rk-recipe-card/rk-recipe-card.js.map +1 -0
- package/dist/collection/components/rk-search-bar/rk-search-bar.js +291 -0
- package/dist/collection/components/rk-search-bar/rk-search-bar.js.map +1 -0
- package/dist/collection/components/rk-segmented-control/rk-segmented-control.js +196 -0
- package/dist/collection/components/rk-segmented-control/rk-segmented-control.js.map +1 -0
- package/dist/collection/components/rk-tag-input/rk-tag-input.js +264 -0
- package/dist/collection/components/rk-tag-input/rk-tag-input.js.map +1 -0
- package/dist/collection/index.js +2 -0
- package/dist/collection/index.js.map +1 -0
- package/dist/components/index.d.ts +35 -0
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/rk-badge.d.ts +11 -0
- package/dist/components/rk-badge.js +2 -0
- package/dist/components/rk-badge.js.map +1 -0
- package/dist/components/rk-chip.d.ts +11 -0
- package/dist/components/rk-chip.js +2 -0
- package/dist/components/rk-chip.js.map +1 -0
- package/dist/components/rk-empty-state.d.ts +11 -0
- package/dist/components/rk-empty-state.js +2 -0
- package/dist/components/rk-empty-state.js.map +1 -0
- package/dist/components/rk-meal-slot.d.ts +11 -0
- package/dist/components/rk-meal-slot.js +2 -0
- package/dist/components/rk-meal-slot.js.map +1 -0
- package/dist/components/rk-modal.d.ts +11 -0
- package/dist/components/rk-modal.js +2 -0
- package/dist/components/rk-modal.js.map +1 -0
- package/dist/components/rk-rating.d.ts +11 -0
- package/dist/components/rk-rating.js +2 -0
- package/dist/components/rk-rating.js.map +1 -0
- package/dist/components/rk-recipe-card.d.ts +11 -0
- package/dist/components/rk-recipe-card.js +2 -0
- package/dist/components/rk-recipe-card.js.map +1 -0
- package/dist/components/rk-search-bar.d.ts +11 -0
- package/dist/components/rk-search-bar.js +2 -0
- package/dist/components/rk-search-bar.js.map +1 -0
- package/dist/components/rk-segmented-control.d.ts +11 -0
- package/dist/components/rk-segmented-control.js +2 -0
- package/dist/components/rk-segmented-control.js.map +1 -0
- package/dist/components/rk-tag-input.d.ts +11 -0
- package/dist/components/rk-tag-input.js +2 -0
- package/dist/components/rk-tag-input.js.map +1 -0
- package/dist/esm/index-Bxo0C8lB.js +1684 -0
- package/dist/esm/index-Bxo0C8lB.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/loader.js +13 -0
- package/dist/esm/loader.js.map +1 -0
- package/dist/esm/recipe-kit.js +23 -0
- package/dist/esm/recipe-kit.js.map +1 -0
- package/dist/esm/rk-badge_10.entry.js +627 -0
- package/dist/esm/rk-badge_10.entry.js.map +1 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.js +1 -0
- package/dist/recipe-kit/index.esm.js +2 -0
- package/dist/recipe-kit/index.esm.js.map +1 -0
- package/dist/recipe-kit/p-Bxo0C8lB.js +3 -0
- package/dist/recipe-kit/p-Bxo0C8lB.js.map +1 -0
- package/dist/recipe-kit/p-fcb955c7.entry.js +2 -0
- package/dist/recipe-kit/p-fcb955c7.entry.js.map +1 -0
- package/dist/recipe-kit/recipe-kit.css +1 -0
- package/dist/recipe-kit/recipe-kit.esm.js +2 -0
- package/dist/recipe-kit/recipe-kit.esm.js.map +1 -0
- package/dist/recipe-kit/rk-global.css +104 -0
- package/dist/types/components/rk-badge/rk-badge.d.ts +15 -0
- package/dist/types/components/rk-chip/rk-chip.d.ts +37 -0
- package/dist/types/components/rk-empty-state/rk-empty-state.d.ts +19 -0
- package/dist/types/components/rk-meal-slot/rk-meal-slot.d.ts +53 -0
- package/dist/types/components/rk-modal/rk-modal.d.ts +41 -0
- package/dist/types/components/rk-rating/rk-rating.d.ts +26 -0
- package/dist/types/components/rk-recipe-card/rk-recipe-card.d.ts +67 -0
- package/dist/types/components/rk-search-bar/rk-search-bar.d.ts +39 -0
- package/dist/types/components/rk-segmented-control/rk-segmented-control.d.ts +37 -0
- package/dist/types/components/rk-tag-input/rk-tag-input.d.ts +35 -0
- package/{packages/recipe-kit-stencil/src → dist/types}/components.d.ts +1 -1
- package/{packages/recipe-kit-stencil/src/index.ts → dist/types/index.d.ts} +0 -2
- package/dist/types/stencil-public-runtime.d.ts +1873 -0
- package/loader/cdn.js +1 -0
- package/loader/index.cjs.js +1 -0
- package/loader/index.d.ts +24 -0
- package/loader/index.es2017.js +1 -0
- package/loader/index.js +2 -0
- package/package.json +84 -22
- package/.claude/settings.local.json +0 -10
- package/.npmr +0 -1
- package/apps/recipe-web/package.json +0 -31
- package/apps/recipe-web/src/app.css +0 -418
- package/apps/recipe-web/src/app.d.ts +0 -32
- package/apps/recipe-web/src/app.html +0 -25
- package/apps/recipe-web/src/lib/api/mealdb.ts +0 -240
- package/apps/recipe-web/src/lib/components/AppHeader.svelte +0 -290
- package/apps/recipe-web/src/lib/components/ConfirmDialog.svelte +0 -46
- package/apps/recipe-web/src/lib/components/Modal.svelte +0 -33
- package/apps/recipe-web/src/lib/components/RecipeCard.svelte +0 -100
- package/apps/recipe-web/src/lib/components/RecipeForm.svelte +0 -560
- package/apps/recipe-web/src/lib/components/RecipeGrid.svelte +0 -77
- package/apps/recipe-web/src/lib/components/RecipePickerDialog.svelte +0 -185
- package/apps/recipe-web/src/lib/components/Toaster.svelte +0 -111
- package/apps/recipe-web/src/lib/domain/recipe.ts +0 -130
- package/apps/recipe-web/src/lib/domain/validation.ts +0 -189
- package/apps/recipe-web/src/lib/stencil/WebComponent.svelte +0 -145
- package/apps/recipe-web/src/lib/stencil/register.ts +0 -28
- package/apps/recipe-web/src/lib/stores/favorites.svelte.ts +0 -81
- package/apps/recipe-web/src/lib/stores/myRecipes.svelte.ts +0 -98
- package/apps/recipe-web/src/lib/stores/persisted.svelte.ts +0 -73
- package/apps/recipe-web/src/lib/stores/planner.svelte.ts +0 -275
- package/apps/recipe-web/src/lib/stores/theme.svelte.ts +0 -67
- package/apps/recipe-web/src/lib/stores/toast.svelte.ts +0 -66
- package/apps/recipe-web/src/routes/+error.svelte +0 -36
- package/apps/recipe-web/src/routes/+layout.svelte +0 -62
- package/apps/recipe-web/src/routes/+page.svelte +0 -352
- package/apps/recipe-web/src/routes/+page.ts +0 -107
- package/apps/recipe-web/src/routes/favorites/+page.svelte +0 -170
- package/apps/recipe-web/src/routes/my-recipes/+layout.ts +0 -55
- package/apps/recipe-web/src/routes/my-recipes/+page.svelte +0 -180
- package/apps/recipe-web/src/routes/my-recipes/[id]/edit/+page.svelte +0 -104
- package/apps/recipe-web/src/routes/my-recipes/new/+page.svelte +0 -67
- package/apps/recipe-web/src/routes/planner/+page.svelte +0 -422
- package/apps/recipe-web/src/routes/recipes/[id]/+page.svelte +0 -538
- package/apps/recipe-web/src/routes/recipes/[id]/+page.ts +0 -24
- package/apps/recipe-web/static/favicon.svg +0 -8
- package/apps/recipe-web/static/robots.txt +0 -2
- package/apps/recipe-web/svelte.config.js +0 -35
- package/apps/recipe-web/tsconfig.json +0 -14
- package/apps/recipe-web/vite.config.ts +0 -23
- package/packages/recipe-kit-stencil/README.md +0 -185
- package/packages/recipe-kit-stencil/package-lock.json +0 -4772
- package/packages/recipe-kit-stencil/package.json +0 -91
- package/packages/recipe-kit-stencil/src/components/rk-badge/readme.md +0 -38
- package/packages/recipe-kit-stencil/src/components/rk-badge/rk-badge.tsx +0 -31
- package/packages/recipe-kit-stencil/src/components/rk-chip/readme.md +0 -52
- package/packages/recipe-kit-stencil/src/components/rk-chip/rk-chip.tsx +0 -105
- package/packages/recipe-kit-stencil/src/components/rk-empty-state/readme.md +0 -44
- package/packages/recipe-kit-stencil/src/components/rk-empty-state/rk-empty-state.tsx +0 -50
- package/packages/recipe-kit-stencil/src/components/rk-meal-slot/readme.md +0 -58
- package/packages/recipe-kit-stencil/src/components/rk-meal-slot/rk-meal-slot.tsx +0 -182
- package/packages/recipe-kit-stencil/src/components/rk-modal/readme.md +0 -57
- package/packages/recipe-kit-stencil/src/components/rk-modal/rk-modal.tsx +0 -176
- package/packages/recipe-kit-stencil/src/components/rk-rating/readme.md +0 -42
- package/packages/recipe-kit-stencil/src/components/rk-rating/rk-rating.tsx +0 -114
- package/packages/recipe-kit-stencil/src/components/rk-recipe-card/readme.md +0 -66
- package/packages/recipe-kit-stencil/src/components/rk-recipe-card/rk-recipe-card.tsx +0 -179
- package/packages/recipe-kit-stencil/src/components/rk-recipe-card/test/rk-recipe-card.spec.tsx +0 -70
- package/packages/recipe-kit-stencil/src/components/rk-search-bar/readme.md +0 -67
- package/packages/recipe-kit-stencil/src/components/rk-search-bar/rk-search-bar.tsx +0 -152
- package/packages/recipe-kit-stencil/src/components/rk-search-bar/test/rk-search-bar.spec.tsx +0 -93
- package/packages/recipe-kit-stencil/src/components/rk-segmented-control/readme.md +0 -41
- package/packages/recipe-kit-stencil/src/components/rk-segmented-control/rk-segmented-control.tsx +0 -115
- package/packages/recipe-kit-stencil/src/components/rk-tag-input/readme.md +0 -47
- package/packages/recipe-kit-stencil/src/components/rk-tag-input/rk-tag-input.tsx +0 -181
- package/packages/recipe-kit-stencil/src/components/rk-tag-input/test/rk-tag-input.spec.tsx +0 -76
- package/packages/recipe-kit-stencil/src/index.html +0 -259
- package/packages/recipe-kit-stencil/stencil.config.ts +0 -41
- package/packages/recipe-kit-stencil/tsconfig.json +0 -24
- /package/{packages/recipe-kit-stencil/LICENSE → LICENSE} +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-badge/rk-badge.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-chip/rk-chip.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-empty-state/rk-empty-state.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-meal-slot/rk-meal-slot.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-modal/rk-modal.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-rating/rk-rating.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-recipe-card/rk-recipe-card.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-search-bar/rk-search-bar.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-segmented-control/rk-segmented-control.css +0 -0
- /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-tag-input/rk-tag-input.css +0 -0
- /package/{packages/recipe-kit-stencil/src/global → dist/collection}/rk-global.css +0 -0
package/README.md
CHANGED
|
@@ -1,354 +1,185 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ssejal8/recipe-ui-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Framework-agnostic web components for recipe discovery, favorites and meal-planning UIs, built with [StencilJS](https://stenciljs.com/).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
| --- | --- |
|
|
7
|
-
| **Live application** | _fill in after deploying — see [Deployment](#deployment)_ |
|
|
8
|
-
| **npm package** | https://www.npmjs.com/package/recipe-kit-stencil |
|
|
9
|
-
| **GitHub repository** | _fill in after pushing — see [Repository](#repository)_ |
|
|
10
|
-
| **Recipe data** | [TheMealDB](https://www.themealdb.com/api.php) (public test key, no signup) |
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Contents
|
|
15
|
-
|
|
16
|
-
- [What it does](#what-it-does)
|
|
17
|
-
- [Repository layout](#repository-layout)
|
|
18
|
-
- [Setup](#setup)
|
|
19
|
-
- [Starting the development server](#starting-the-development-server)
|
|
20
|
-
- [The Stencil component library](#the-stencil-component-library)
|
|
21
|
-
- [Publishing to npm](#publishing-to-npm)
|
|
22
|
-
- [How SvelteKit consumes the package](#how-sveltekit-consumes-the-package)
|
|
23
|
-
- [Architecture](#architecture)
|
|
24
|
-
- [Deployment](#deployment)
|
|
25
|
-
- [Repository](#repository)
|
|
26
|
-
- [Assumptions and decisions](#assumptions-and-decisions)
|
|
27
|
-
- [Known limitations](#known-limitations)
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## What it does
|
|
32
|
-
|
|
33
|
-
**Recipe discovery** — Search the catalogue by name, browse by category chip, and narrow results by category, cuisine and main ingredient. Every filter lives in the URL, so a search is shareable and survives a reload. Results can be sorted, and are paged client-side.
|
|
34
|
-
|
|
35
|
-
**Recipe details** — A dedicated page per recipe with the full ingredient list (as a tickable prep checklist with progress), numbered method steps, cuisine metadata, and links to the original source and video where the API provides them.
|
|
36
|
-
|
|
37
|
-
**Recipe management** — Create, edit and delete your own recipes through a validated form. Your recipes are searchable alongside the catalogue, appear first in results, and are marked with a badge. Deletes are undoable.
|
|
38
|
-
|
|
39
|
-
**Favorites** — Favorite from any card or from the detail page, filter your saved list by text and category, and clear the list (also undoable).
|
|
40
|
-
|
|
41
|
-
**Weekly meal planner** — A 7-day × 3-meal grid for any week, with previous/next week navigation, "repeat last week", drag-and-drop between slots, a recipe picker that searches your favorites / your own recipes / the whole catalogue, and a per-week recipe summary.
|
|
42
|
-
|
|
43
|
-
Favorites, your recipes and meal plans persist in `localStorage` and stay in sync across duplicate browser tabs.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Repository layout
|
|
5
|
+
The library ships **standard custom elements**, so it works in SvelteKit, React, Vue, Angular or plain HTML with no framework-specific wrapper. It is consumed by the [Recipe Finder & Meal Planner](https://github.com/sejal-sharma/recipe-ui) SvelteKit application.
|
|
48
6
|
|
|
49
7
|
```
|
|
50
|
-
recipe-ui
|
|
51
|
-
├── packages/
|
|
52
|
-
│ └── recipe-kit-stencil/ StencilJS component library → published to npm
|
|
53
|
-
│ ├── src/components/ 10 web components (.tsx + .css + specs)
|
|
54
|
-
│ ├── src/global/ design tokens (--rk-* custom properties)
|
|
55
|
-
│ ├── src/index.html component gallery / dev harness
|
|
56
|
-
│ └── stencil.config.ts
|
|
57
|
-
└── apps/
|
|
58
|
-
└── recipe-web/ SvelteKit 5 application
|
|
59
|
-
├── src/lib/api/ typed TheMealDB client
|
|
60
|
-
├── src/lib/domain/ recipe model + validation rules
|
|
61
|
-
├── src/lib/stencil/ the SvelteKit ↔ Stencil integration layer
|
|
62
|
-
├── src/lib/stores/ Svelte 5 rune stores, localStorage-backed
|
|
63
|
-
├── src/lib/components/ app-level Svelte components
|
|
64
|
-
└── src/routes/ pages
|
|
8
|
+
npm install @ssejal8/recipe-ui-kit
|
|
65
9
|
```
|
|
66
10
|
|
|
67
11
|
---
|
|
68
12
|
|
|
69
|
-
##
|
|
70
|
-
|
|
71
|
-
**Requirements:** Node.js ≥ 20 and npm ≥ 10.
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
git clone <your-repo-url> recipe-ui
|
|
75
|
-
cd recipe-ui
|
|
76
|
-
npm install
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
A single `npm install` at the root installs both workspaces. The component library's `prepare` script builds it automatically, so the app has something to import straight away.
|
|
13
|
+
## Quick start
|
|
80
14
|
|
|
81
|
-
|
|
15
|
+
### 1. Register the elements
|
|
82
16
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
## Starting the development server
|
|
17
|
+
```js
|
|
18
|
+
import { defineCustomElements } from '@ssejal8/recipe-ui-kit/loader';
|
|
86
19
|
|
|
87
|
-
|
|
88
|
-
npm run dev
|
|
20
|
+
defineCustomElements();
|
|
89
21
|
```
|
|
90
22
|
|
|
91
|
-
|
|
23
|
+
`defineCustomElements()` registers every tag lazily — the code for a component is only fetched when that tag first appears in the DOM.
|
|
92
24
|
|
|
93
|
-
|
|
25
|
+
### 2. Load the design tokens
|
|
94
26
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
| `npm run dev:app` | App dev server only (library already built) |
|
|
99
|
-
| `npm run dev:kit` | Library in watch mode + component gallery |
|
|
100
|
-
| `npm run build` | Production build of the library, then the app |
|
|
101
|
-
| `npm run check` | `svelte-check` type and template checking |
|
|
102
|
-
| `npm test` | Component library spec tests |
|
|
103
|
-
| `npm run publish:kit` | Publish the library to npm |
|
|
27
|
+
```js
|
|
28
|
+
import '@ssejal8/recipe-ui-kit/dist/recipe-kit/recipe-kit.css';
|
|
29
|
+
```
|
|
104
30
|
|
|
105
|
-
|
|
31
|
+
This stylesheet defines the `--rk-*` custom properties. Because custom properties inherit through the shadow boundary, it is the single place to re-theme the whole library.
|
|
106
32
|
|
|
107
|
-
|
|
33
|
+
### 3. Use the elements
|
|
108
34
|
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
35
|
+
```html
|
|
36
|
+
<rk-recipe-card
|
|
37
|
+
recipe-id="52772"
|
|
38
|
+
name="Teriyaki Chicken Casserole"
|
|
39
|
+
image="https://…/teriyaki.jpg"
|
|
40
|
+
category="Chicken"
|
|
41
|
+
area="Japanese"
|
|
42
|
+
minutes="45"
|
|
43
|
+
>
|
|
44
|
+
<rk-badge tone="accent" small>weeknight</rk-badge>
|
|
45
|
+
<button slot="actions">Add to plan</button>
|
|
46
|
+
</rk-recipe-card>
|
|
112
47
|
```
|
|
113
48
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
## The Stencil component library
|
|
49
|
+
### SSR / SvelteKit note
|
|
117
50
|
|
|
118
|
-
|
|
51
|
+
Custom elements are a browser API, so registration must happen on the client only:
|
|
119
52
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
| `rk-chip` | Category rail, active-filter pills, favorites facets | `rkChipToggle`, `rkChipDismiss` |
|
|
125
|
-
| `rk-segmented-control` | Sort controls, picker source switch | `rkSegmentChange` |
|
|
126
|
-
| `rk-meal-slot` | All 21 cells of the planner grid | `rkMealAdd`, `rkMealSwap`, `rkMealRemove`, `rkMealOpen`, `rkMealDrop` |
|
|
127
|
-
| `rk-modal` | Picker, add-to-plan, all confirmations | `rkModalClose` |
|
|
128
|
-
| `rk-tag-input` | Tag field in the recipe form | `rkTagsChange` |
|
|
129
|
-
| `rk-badge` | Metadata badges throughout | — |
|
|
130
|
-
| `rk-rating` | Star rating display | `rkRatingChange` |
|
|
131
|
-
| `rk-empty-state` | Every empty and error state | — |
|
|
132
|
-
|
|
133
|
-
Run the gallery to see and exercise all of them, with a live event log:
|
|
53
|
+
```svelte
|
|
54
|
+
<script>
|
|
55
|
+
import { browser } from '$app/environment';
|
|
56
|
+
import { onMount } from 'svelte';
|
|
134
57
|
|
|
135
|
-
|
|
136
|
-
|
|
58
|
+
onMount(async () => {
|
|
59
|
+
const { defineCustomElements } = await import('@ssejal8/recipe-ui-kit/loader');
|
|
60
|
+
await defineCustomElements();
|
|
61
|
+
});
|
|
62
|
+
</script>
|
|
137
63
|
```
|
|
138
64
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
- **Primitive props over object props.** `rk-recipe-card` takes `name`, `image`, `category` … as separate primitives rather than one `recipe` object, so it works from plain HTML attributes in any framework.
|
|
142
|
-
- **Components are controlled.** They emit intent and never mutate their own state. `rkFavoriteToggle` reports the state to move *to*; the app persists it and passes `favorite` back down.
|
|
143
|
-
- **Shadow DOM everywhere.** Styling hooks are deliberate: `--rk-*` custom properties and `part="…"` attributes.
|
|
144
|
-
- **Complex props have a string fallback.** `options` and `values` are arrays, assigned as DOM properties on the normal path, but both also parse a JSON string attribute.
|
|
65
|
+
Unknown tags render as inert elements during SSR and hydrate once the definitions land.
|
|
145
66
|
|
|
146
67
|
---
|
|
147
68
|
|
|
148
|
-
##
|
|
69
|
+
## Components
|
|
149
70
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
71
|
+
| Tag | Purpose | Key events |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| `<rk-search-bar>` | Debounced search field with clear button and an `actions` slot | `rkSearch`, `rkClear` |
|
|
74
|
+
| `<rk-chip>` | Selectable / dismissible filter chip | `rkChipToggle`, `rkChipDismiss` |
|
|
75
|
+
| `<rk-segmented-control>` | Single-select segmented control (sorting, view switching) | `rkSegmentChange` |
|
|
76
|
+
| `<rk-recipe-card>` | Recipe tile with favorite toggle, badges slot and actions slot | `rkCardSelect`, `rkFavoriteToggle` |
|
|
77
|
+
| `<rk-rating>` | Star rating, read-only or interactive | `rkRatingChange` |
|
|
78
|
+
| `<rk-badge>` | Small slot-driven label with tone/variant options | — |
|
|
79
|
+
| `<rk-tag-input>` | Tag / token editor with de-duplication and a max-tags cap | `rkTagsChange` |
|
|
80
|
+
| `<rk-meal-slot>` | One planner cell: add target, filled slot, and drop zone | `rkMealAdd`, `rkMealSwap`, `rkMealRemove`, `rkMealOpen`, `rkMealDrop` |
|
|
81
|
+
| `<rk-modal>` | Accessible dialog with focus trap, Escape handling and slots | `rkModalClose` |
|
|
82
|
+
| `<rk-empty-state>` | Empty / error placeholder with an actions slot | — |
|
|
83
|
+
|
|
84
|
+
Per-component prop, event, slot and CSS-part tables are generated from source into each component folder — see [`src/components`](./src/components).
|
|
155
85
|
|
|
156
|
-
|
|
157
|
-
npm run publish:kit
|
|
158
|
-
```
|
|
86
|
+
---
|
|
159
87
|
|
|
160
|
-
|
|
88
|
+
## Design principles
|
|
161
89
|
|
|
162
|
-
|
|
90
|
+
**Primitive props over object props.** `<rk-recipe-card>` takes `name`, `image`, `category` … as separate primitive properties rather than one `recipe` object. Primitives round-trip through HTML attributes, so the component is usable from plain markup in any framework.
|
|
163
91
|
|
|
164
|
-
|
|
92
|
+
**Complex props accept a string fallback.** `rk-segmented-control.options` and `rk-tag-input.values` are arrays. Assign them as DOM properties for the normal path; both also parse a JSON-encoded (or comma-separated) string attribute for consumers that cannot set properties.
|
|
165
93
|
|
|
166
|
-
|
|
167
|
-
| --- | --- |
|
|
168
|
-
| Bug fix, styling correction, no API change | `npm run release:patch` |
|
|
169
|
-
| New component, new optional prop, new event | `npm run release:minor` |
|
|
170
|
-
| Renamed/removed prop, event or tag; changed payload | `npm run release:major` |
|
|
94
|
+
**Components are controlled.** They emit an intent and let the host application own the state. `rkFavoriteToggle` reports the state to move *to*; the app persists it and passes `favorite` back down. Nothing is silently mutated behind the application's back.
|
|
171
95
|
|
|
172
|
-
|
|
96
|
+
**No global CSS leakage.** Every component uses shadow DOM. Styling hooks are exposed deliberately, via `--rk-*` custom properties and `part="…"` attributes.
|
|
173
97
|
|
|
174
98
|
---
|
|
175
99
|
|
|
176
|
-
##
|
|
100
|
+
## Theming
|
|
177
101
|
|
|
178
|
-
|
|
102
|
+
Override any token on `:root` — or on any ancestor, to scope a theme to a subtree:
|
|
179
103
|
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
104
|
+
```css
|
|
105
|
+
:root {
|
|
106
|
+
--rk-primary: #7c3aed;
|
|
107
|
+
--rk-radius-lg: 8px;
|
|
108
|
+
--rk-font-display: 'Fraunces', serif;
|
|
184
109
|
}
|
|
185
110
|
```
|
|
186
111
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
```ts
|
|
190
|
-
import 'recipe-kit-stencil/dist/recipe-kit/recipe-kit.css'; // design tokens
|
|
191
|
-
const { defineCustomElements } = await import('recipe-kit-stencil/loader');
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
Because both directories are npm workspaces, `npm install` satisfies `^0.1.0` from the local package during development — one install, no `npm link`, no manual rebuild step. Outside this repository the identical range resolves from the registry.
|
|
195
|
-
|
|
196
|
-
To prove the registry path end-to-end after publishing:
|
|
197
|
-
|
|
198
|
-
```bash
|
|
199
|
-
cd /tmp && mkdir kit-check && cd kit-check && npm init -y
|
|
200
|
-
npm install recipe-kit-stencil --registry=https://registry.npmjs.org/
|
|
201
|
-
ls node_modules/recipe-kit-stencil/loader
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### Passing data down: properties, not attributes
|
|
112
|
+
A dark palette is included and activates on `<html data-rk-theme="dark">` (or `data-theme="dark"`).
|
|
205
113
|
|
|
206
|
-
|
|
114
|
+
For finer control, target the exposed shadow parts:
|
|
207
115
|
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
props={{ recipeId: recipe.id, name: recipe.name, favorite: isFavorite }}
|
|
212
|
-
events={{ rkFavoriteToggle: handleToggle }}
|
|
213
|
-
>
|
|
214
|
-
<rk-badge tone="accent" small>30 min</rk-badge>
|
|
215
|
-
{#snippet actions()}
|
|
216
|
-
<button slot="actions" class="btn btn-sm">Add to plan</button>
|
|
217
|
-
{/snippet}
|
|
218
|
-
</WebComponent>
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
It does three things that matter:
|
|
222
|
-
|
|
223
|
-
1. **Defers property writes until the element upgrades.** Assigning a property before `customElements.define` runs would shadow the generated accessor, so writes wait on `customElements.whenDefined(tag)`.
|
|
224
|
-
2. **Registers listeners with `addEventListener`.** The library emits `rkFavoriteToggle`, not `rkfavoritetoggle`; `addEventListener` preserves case exactly, keeping the app's contract identical to the library's documented one.
|
|
225
|
-
3. **Mirrors props as kebab-case attributes for SSR.** Property assignment cannot happen on the server, which would leave every card an empty shell until hydration. Stencil observes attributes, so elements render with correct content in the server response — before hydration, and with JavaScript disabled.
|
|
226
|
-
|
|
227
|
-
### Handling events coming back up
|
|
228
|
-
|
|
229
|
-
Custom events are handled as ordinary app logic. The store is treated as the source of truth rather than the event payload, so a change made in another tab cannot be clobbered:
|
|
230
|
-
|
|
231
|
-
```ts
|
|
232
|
-
function handleFavoriteToggle(event: CustomEvent<{ favorite: boolean }>) {
|
|
233
|
-
const nowFavorite = favorites.toggle(recipe);
|
|
234
|
-
if (nowFavorite !== event.detail.favorite) return;
|
|
235
|
-
toasts.push(nowFavorite ? `Saved “${recipe.name}”.` : `Removed “${recipe.name}”.`);
|
|
116
|
+
```css
|
|
117
|
+
rk-recipe-card::part(title) {
|
|
118
|
+
text-transform: uppercase;
|
|
236
119
|
}
|
|
237
120
|
```
|
|
238
121
|
|
|
239
|
-
### Slots
|
|
240
|
-
|
|
241
|
-
- **Default slot** — badges under a card title (`rk-recipe-card`), call-to-action buttons (`rk-empty-state`), dialog body (`rk-modal`).
|
|
242
|
-
- **Named slots** — `actions` on `rk-recipe-card` for per-card buttons, `actions` on `rk-search-bar` for the result counter, `footer` on `rk-modal` for every confirmation's button row.
|
|
243
|
-
|
|
244
|
-
Slotted content is passed with Svelte 5 snippets. Any element needing a `slot="…"` attribute must sit inside a `{#snippet}` block, because Svelte 5 treats `slot` on a direct child of a *component* as the deprecated slot syntax.
|
|
245
|
-
|
|
246
122
|
---
|
|
247
123
|
|
|
248
|
-
##
|
|
124
|
+
## Events
|
|
249
125
|
|
|
250
|
-
|
|
126
|
+
Every event name is prefixed `rk` and bubbles with `composed: true`, so it can be handled on an ancestor rather than each element:
|
|
251
127
|
|
|
128
|
+
```js
|
|
129
|
+
document.addEventListener('rkFavoriteToggle', (event) => {
|
|
130
|
+
const { recipeId, favorite } = event.detail;
|
|
131
|
+
});
|
|
252
132
|
```
|
|
253
|
-
TheMealDB ──► src/lib/api/mealdb.ts ──► +page.ts load ──► page component ──► rk-* elements
|
|
254
|
-
(typed, normalised) (SSR + CSR) │ │
|
|
255
|
-
│ props down │ events up
|
|
256
|
-
localStorage ──► rune stores ─────────────────────────────────┘◄───────────────────┘
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
**One recipe model.** TheMealDB returns 20 flat `strIngredient1…20` / `strMeasure1…20` column pairs, inconsistent instruction formatting, and comma-joined tags. `src/lib/api/mealdb.ts` normalises all of it into a single `Recipe` type that user-authored recipes also satisfy, so no component branches on data origin — only on whether the user *owns* a recipe.
|
|
260
|
-
|
|
261
|
-
**Filters live in the URL.** Search state is read from `url.searchParams` inside `load`, which means SvelteKit re-runs the fetch on change and no fetch logic is duplicated in the component.
|
|
262
|
-
|
|
263
|
-
**Combined filters via set intersection.** The API only supports one filter dimension per request, so each active filter becomes one `filter.php` call and the resulting id sets are intersected. That is what makes "Seafood + Italian" work.
|
|
264
|
-
|
|
265
|
-
**Stores are classes with explicit persistence.** Stores are created at module scope, outside any component, where `$effect` has no owner — so each mutation calls `save()` rather than relying on an effect. A `storage` listener keeps duplicate tabs in sync.
|
|
266
|
-
|
|
267
|
-
**Referential integrity is owned by the store, not the pages.** Favorites and planner slots hold a `RecipeSummary` snapshot so those views render without a network round-trip per recipe. `myRecipes.update()` and `.remove()` therefore propagate renames and deletions into favorites and every planned slot, so no caller can forget to.
|
|
268
133
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
`src/lib/domain/validation.ts` is pure and Svelte-free, so the same rules could run on a server later. It enforces: required name (3–120 chars), required category and cuisine, valid `http(s)` image URL if given, ≥ 2 ingredients with no case-insensitive duplicates, ≥ 1 instruction step, optional whole-number time and servings within range, ≤ 6 tags, and a notes length cap. Duplicate recipe titles are checked separately, in the page, because that rule depends on store contents rather than input shape.
|
|
272
|
-
|
|
273
|
-
Errors surface only after the first submit attempt, then live on every keystroke, with a count banner and focus moved to the first invalid field.
|
|
274
|
-
|
|
275
|
-
### Rendering
|
|
276
|
-
|
|
277
|
-
Discover, recipe details and the facet lists are server-rendered. Anything backed by `localStorage` — favorites, your recipes, the planner — is client-only by nature, and each page handles the pre-hydration window explicitly rather than flashing an incorrect empty state.
|
|
278
|
-
|
|
279
|
-
---
|
|
134
|
+
Payload types are exported for TypeScript consumers:
|
|
280
135
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
`adapter-auto` is the default: on Vercel, Netlify or Cloudflare Pages it installs the right adapter with no configuration.
|
|
284
|
-
|
|
285
|
-
### Vercel or Netlify (recommended)
|
|
286
|
-
|
|
287
|
-
Import the repository, then set:
|
|
288
|
-
|
|
289
|
-
| Setting | Value |
|
|
290
|
-
| --- | --- |
|
|
291
|
-
| Root directory / base | `apps/recipe-web` |
|
|
292
|
-
| Install command | `npm install` |
|
|
293
|
-
| Build command | `npm run build` |
|
|
294
|
-
|
|
295
|
-
npm workspaces are detected from the repository root, so the install reaches both packages. Once the library is published, the app installs it from the registry and no library build step is needed at all.
|
|
296
|
-
|
|
297
|
-
### Self-hosted Node
|
|
298
|
-
|
|
299
|
-
```bash
|
|
300
|
-
npm run build:kit
|
|
301
|
-
ADAPTER=node npm run build --workspace recipe-web
|
|
302
|
-
node apps/recipe-web/build/index.js # PORT and HOST are respected
|
|
136
|
+
```ts
|
|
137
|
+
import type { RkFavoriteToggleDetail, RkMealDropDetail, RkSegmentOption } from '@ssejal8/recipe-ui-kit';
|
|
303
138
|
```
|
|
304
139
|
|
|
305
|
-
Verified locally: the Node build boots and serves every route.
|
|
306
|
-
|
|
307
140
|
---
|
|
308
141
|
|
|
309
|
-
##
|
|
142
|
+
## Local development
|
|
310
143
|
|
|
311
144
|
```bash
|
|
312
|
-
|
|
313
|
-
|
|
145
|
+
npm install
|
|
146
|
+
npm start # dev server with the component gallery at src/index.html
|
|
147
|
+
npm run build # production build + generated docs
|
|
148
|
+
npm test # spec tests
|
|
314
149
|
```
|
|
315
150
|
|
|
316
|
-
|
|
151
|
+
`npm start` serves a gallery page that renders every component and logs every emitted event — useful for verifying behaviour outside the consuming app.
|
|
317
152
|
|
|
318
153
|
---
|
|
319
154
|
|
|
320
|
-
##
|
|
321
|
-
|
|
322
|
-
**TheMealDB was chosen over Spoonacular or Edamam** because it needs no API key or signup, which keeps the project clone-and-run. Consequences: it has no ratings, no cook times and no nutrition data, and no write endpoints.
|
|
155
|
+
## Output targets
|
|
323
156
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
**The default Discover view is a small featured set.** `search.php` with an empty query returns 25 recipes, not the full catalogue. Rather than firing 14 parallel category requests on every page load, the default view is labelled "Featured recipes" and a category chip rail gives one-tap access to each full category list.
|
|
329
|
-
|
|
330
|
-
**The planner is week-addressable.** Plans are keyed by the Monday of their week, so past and future weeks are all navigable and "repeat last week" is possible, rather than there being a single perpetual week.
|
|
331
|
-
|
|
332
|
-
**Ratings are display-only.** `rk-rating` is fully interactive, but the app never persists a user rating: TheMealDB has no rating data to seed from, and a rating that exists only in one browser would be misleading. The component is in the library for completeness and is exercised in the gallery.
|
|
157
|
+
| Target | Output | Use case |
|
|
158
|
+
| --- | --- | --- |
|
|
159
|
+
| `dist` | `dist/`, `loader/` | Lazy-loaded elements. The default, and what the SvelteKit app uses. |
|
|
160
|
+
| `dist-custom-elements` | `dist/components/*.js` | Import and register a single component; tree-shakeable. |
|
|
333
161
|
|
|
334
|
-
|
|
162
|
+
```js
|
|
163
|
+
// Single-component import
|
|
164
|
+
import { RkRecipeCard } from '@ssejal8/recipe-ui-kit/components/rk-recipe-card';
|
|
165
|
+
```
|
|
335
166
|
|
|
336
|
-
|
|
167
|
+
---
|
|
337
168
|
|
|
338
|
-
|
|
169
|
+
## Versioning
|
|
339
170
|
|
|
340
|
-
|
|
171
|
+
[Semantic versioning](https://semver.org/):
|
|
341
172
|
|
|
342
|
-
|
|
173
|
+
- **patch** — bug fixes, styling corrections, no API change
|
|
174
|
+
- **minor** — new components, new optional props, new events
|
|
175
|
+
- **major** — renamed or removed props/events/tags, changed event payloads
|
|
343
176
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
- **Ingredient filtering of your own recipes is a substring match**, while the API's ingredient filter is an exact match on its own ingredient vocabulary. Results from the two sources can differ in strictness for the same query.
|
|
348
|
-
- **No pagination against the API.** TheMealDB returns whole result sets, so paging is client-side over the full list.
|
|
177
|
+
```bash
|
|
178
|
+
npm run release:patch # or release:minor / release:major
|
|
179
|
+
```
|
|
349
180
|
|
|
350
|
-
|
|
181
|
+
`prepublishOnly` runs a clean production build, so a published tarball can never contain stale output.
|
|
351
182
|
|
|
352
183
|
## License
|
|
353
184
|
|
|
354
|
-
MIT
|
|
185
|
+
MIT © Sejal Sharma
|