@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.
Files changed (191) hide show
  1. package/README.md +108 -277
  2. package/dist/cjs/index-BEr8BsSA.js +1712 -0
  3. package/dist/cjs/index-BEr8BsSA.js.map +1 -0
  4. package/dist/cjs/index.cjs.js +5 -0
  5. package/dist/cjs/index.cjs.js.map +1 -0
  6. package/dist/cjs/loader.cjs.js +15 -0
  7. package/dist/cjs/loader.cjs.js.map +1 -0
  8. package/dist/cjs/recipe-kit.cjs.js +27 -0
  9. package/dist/cjs/recipe-kit.cjs.js.map +1 -0
  10. package/dist/cjs/rk-badge_10.cjs.entry.js +638 -0
  11. package/dist/cjs/rk-badge_10.cjs.entry.js.map +1 -0
  12. package/dist/collection/collection-manifest.json +22 -0
  13. package/dist/collection/components/rk-badge/rk-badge.js +97 -0
  14. package/dist/collection/components/rk-badge/rk-badge.js.map +1 -0
  15. package/dist/collection/components/rk-chip/rk-chip.js +245 -0
  16. package/dist/collection/components/rk-chip/rk-chip.js.map +1 -0
  17. package/dist/collection/components/rk-empty-state/rk-empty-state.js +138 -0
  18. package/dist/collection/components/rk-empty-state/rk-empty-state.js.map +1 -0
  19. package/dist/collection/components/rk-meal-slot/rk-meal-slot.js +312 -0
  20. package/dist/collection/components/rk-meal-slot/rk-meal-slot.js.map +1 -0
  21. package/dist/collection/components/rk-modal/rk-modal.js +256 -0
  22. package/dist/collection/components/rk-modal/rk-modal.js.map +1 -0
  23. package/dist/collection/components/rk-rating/rk-rating.js +228 -0
  24. package/dist/collection/components/rk-rating/rk-rating.js.map +1 -0
  25. package/dist/collection/components/rk-recipe-card/rk-recipe-card.js +366 -0
  26. package/dist/collection/components/rk-recipe-card/rk-recipe-card.js.map +1 -0
  27. package/dist/collection/components/rk-search-bar/rk-search-bar.js +291 -0
  28. package/dist/collection/components/rk-search-bar/rk-search-bar.js.map +1 -0
  29. package/dist/collection/components/rk-segmented-control/rk-segmented-control.js +196 -0
  30. package/dist/collection/components/rk-segmented-control/rk-segmented-control.js.map +1 -0
  31. package/dist/collection/components/rk-tag-input/rk-tag-input.js +264 -0
  32. package/dist/collection/components/rk-tag-input/rk-tag-input.js.map +1 -0
  33. package/dist/collection/index.js +2 -0
  34. package/dist/collection/index.js.map +1 -0
  35. package/dist/components/index.d.ts +35 -0
  36. package/dist/components/index.js +2 -0
  37. package/dist/components/index.js.map +1 -0
  38. package/dist/components/rk-badge.d.ts +11 -0
  39. package/dist/components/rk-badge.js +2 -0
  40. package/dist/components/rk-badge.js.map +1 -0
  41. package/dist/components/rk-chip.d.ts +11 -0
  42. package/dist/components/rk-chip.js +2 -0
  43. package/dist/components/rk-chip.js.map +1 -0
  44. package/dist/components/rk-empty-state.d.ts +11 -0
  45. package/dist/components/rk-empty-state.js +2 -0
  46. package/dist/components/rk-empty-state.js.map +1 -0
  47. package/dist/components/rk-meal-slot.d.ts +11 -0
  48. package/dist/components/rk-meal-slot.js +2 -0
  49. package/dist/components/rk-meal-slot.js.map +1 -0
  50. package/dist/components/rk-modal.d.ts +11 -0
  51. package/dist/components/rk-modal.js +2 -0
  52. package/dist/components/rk-modal.js.map +1 -0
  53. package/dist/components/rk-rating.d.ts +11 -0
  54. package/dist/components/rk-rating.js +2 -0
  55. package/dist/components/rk-rating.js.map +1 -0
  56. package/dist/components/rk-recipe-card.d.ts +11 -0
  57. package/dist/components/rk-recipe-card.js +2 -0
  58. package/dist/components/rk-recipe-card.js.map +1 -0
  59. package/dist/components/rk-search-bar.d.ts +11 -0
  60. package/dist/components/rk-search-bar.js +2 -0
  61. package/dist/components/rk-search-bar.js.map +1 -0
  62. package/dist/components/rk-segmented-control.d.ts +11 -0
  63. package/dist/components/rk-segmented-control.js +2 -0
  64. package/dist/components/rk-segmented-control.js.map +1 -0
  65. package/dist/components/rk-tag-input.d.ts +11 -0
  66. package/dist/components/rk-tag-input.js +2 -0
  67. package/dist/components/rk-tag-input.js.map +1 -0
  68. package/dist/esm/index-Bxo0C8lB.js +1684 -0
  69. package/dist/esm/index-Bxo0C8lB.js.map +1 -0
  70. package/dist/esm/index.js +4 -0
  71. package/dist/esm/index.js.map +1 -0
  72. package/dist/esm/loader.js +13 -0
  73. package/dist/esm/loader.js.map +1 -0
  74. package/dist/esm/recipe-kit.js +23 -0
  75. package/dist/esm/recipe-kit.js.map +1 -0
  76. package/dist/esm/rk-badge_10.entry.js +627 -0
  77. package/dist/esm/rk-badge_10.entry.js.map +1 -0
  78. package/dist/index.cjs.js +1 -0
  79. package/dist/index.js +1 -0
  80. package/dist/recipe-kit/index.esm.js +2 -0
  81. package/dist/recipe-kit/index.esm.js.map +1 -0
  82. package/dist/recipe-kit/p-Bxo0C8lB.js +3 -0
  83. package/dist/recipe-kit/p-Bxo0C8lB.js.map +1 -0
  84. package/dist/recipe-kit/p-fcb955c7.entry.js +2 -0
  85. package/dist/recipe-kit/p-fcb955c7.entry.js.map +1 -0
  86. package/dist/recipe-kit/recipe-kit.css +1 -0
  87. package/dist/recipe-kit/recipe-kit.esm.js +2 -0
  88. package/dist/recipe-kit/recipe-kit.esm.js.map +1 -0
  89. package/dist/recipe-kit/rk-global.css +104 -0
  90. package/dist/types/components/rk-badge/rk-badge.d.ts +15 -0
  91. package/dist/types/components/rk-chip/rk-chip.d.ts +37 -0
  92. package/dist/types/components/rk-empty-state/rk-empty-state.d.ts +19 -0
  93. package/dist/types/components/rk-meal-slot/rk-meal-slot.d.ts +53 -0
  94. package/dist/types/components/rk-modal/rk-modal.d.ts +41 -0
  95. package/dist/types/components/rk-rating/rk-rating.d.ts +26 -0
  96. package/dist/types/components/rk-recipe-card/rk-recipe-card.d.ts +67 -0
  97. package/dist/types/components/rk-search-bar/rk-search-bar.d.ts +39 -0
  98. package/dist/types/components/rk-segmented-control/rk-segmented-control.d.ts +37 -0
  99. package/dist/types/components/rk-tag-input/rk-tag-input.d.ts +35 -0
  100. package/{packages/recipe-kit-stencil/src → dist/types}/components.d.ts +1 -1
  101. package/{packages/recipe-kit-stencil/src/index.ts → dist/types/index.d.ts} +0 -2
  102. package/dist/types/stencil-public-runtime.d.ts +1873 -0
  103. package/loader/cdn.js +1 -0
  104. package/loader/index.cjs.js +1 -0
  105. package/loader/index.d.ts +24 -0
  106. package/loader/index.es2017.js +1 -0
  107. package/loader/index.js +2 -0
  108. package/package.json +84 -22
  109. package/.claude/settings.local.json +0 -10
  110. package/.npmr +0 -1
  111. package/apps/recipe-web/package.json +0 -31
  112. package/apps/recipe-web/src/app.css +0 -418
  113. package/apps/recipe-web/src/app.d.ts +0 -32
  114. package/apps/recipe-web/src/app.html +0 -25
  115. package/apps/recipe-web/src/lib/api/mealdb.ts +0 -240
  116. package/apps/recipe-web/src/lib/components/AppHeader.svelte +0 -290
  117. package/apps/recipe-web/src/lib/components/ConfirmDialog.svelte +0 -46
  118. package/apps/recipe-web/src/lib/components/Modal.svelte +0 -33
  119. package/apps/recipe-web/src/lib/components/RecipeCard.svelte +0 -100
  120. package/apps/recipe-web/src/lib/components/RecipeForm.svelte +0 -560
  121. package/apps/recipe-web/src/lib/components/RecipeGrid.svelte +0 -77
  122. package/apps/recipe-web/src/lib/components/RecipePickerDialog.svelte +0 -185
  123. package/apps/recipe-web/src/lib/components/Toaster.svelte +0 -111
  124. package/apps/recipe-web/src/lib/domain/recipe.ts +0 -130
  125. package/apps/recipe-web/src/lib/domain/validation.ts +0 -189
  126. package/apps/recipe-web/src/lib/stencil/WebComponent.svelte +0 -145
  127. package/apps/recipe-web/src/lib/stencil/register.ts +0 -28
  128. package/apps/recipe-web/src/lib/stores/favorites.svelte.ts +0 -81
  129. package/apps/recipe-web/src/lib/stores/myRecipes.svelte.ts +0 -98
  130. package/apps/recipe-web/src/lib/stores/persisted.svelte.ts +0 -73
  131. package/apps/recipe-web/src/lib/stores/planner.svelte.ts +0 -275
  132. package/apps/recipe-web/src/lib/stores/theme.svelte.ts +0 -67
  133. package/apps/recipe-web/src/lib/stores/toast.svelte.ts +0 -66
  134. package/apps/recipe-web/src/routes/+error.svelte +0 -36
  135. package/apps/recipe-web/src/routes/+layout.svelte +0 -62
  136. package/apps/recipe-web/src/routes/+page.svelte +0 -352
  137. package/apps/recipe-web/src/routes/+page.ts +0 -107
  138. package/apps/recipe-web/src/routes/favorites/+page.svelte +0 -170
  139. package/apps/recipe-web/src/routes/my-recipes/+layout.ts +0 -55
  140. package/apps/recipe-web/src/routes/my-recipes/+page.svelte +0 -180
  141. package/apps/recipe-web/src/routes/my-recipes/[id]/edit/+page.svelte +0 -104
  142. package/apps/recipe-web/src/routes/my-recipes/new/+page.svelte +0 -67
  143. package/apps/recipe-web/src/routes/planner/+page.svelte +0 -422
  144. package/apps/recipe-web/src/routes/recipes/[id]/+page.svelte +0 -538
  145. package/apps/recipe-web/src/routes/recipes/[id]/+page.ts +0 -24
  146. package/apps/recipe-web/static/favicon.svg +0 -8
  147. package/apps/recipe-web/static/robots.txt +0 -2
  148. package/apps/recipe-web/svelte.config.js +0 -35
  149. package/apps/recipe-web/tsconfig.json +0 -14
  150. package/apps/recipe-web/vite.config.ts +0 -23
  151. package/packages/recipe-kit-stencil/README.md +0 -185
  152. package/packages/recipe-kit-stencil/package-lock.json +0 -4772
  153. package/packages/recipe-kit-stencil/package.json +0 -91
  154. package/packages/recipe-kit-stencil/src/components/rk-badge/readme.md +0 -38
  155. package/packages/recipe-kit-stencil/src/components/rk-badge/rk-badge.tsx +0 -31
  156. package/packages/recipe-kit-stencil/src/components/rk-chip/readme.md +0 -52
  157. package/packages/recipe-kit-stencil/src/components/rk-chip/rk-chip.tsx +0 -105
  158. package/packages/recipe-kit-stencil/src/components/rk-empty-state/readme.md +0 -44
  159. package/packages/recipe-kit-stencil/src/components/rk-empty-state/rk-empty-state.tsx +0 -50
  160. package/packages/recipe-kit-stencil/src/components/rk-meal-slot/readme.md +0 -58
  161. package/packages/recipe-kit-stencil/src/components/rk-meal-slot/rk-meal-slot.tsx +0 -182
  162. package/packages/recipe-kit-stencil/src/components/rk-modal/readme.md +0 -57
  163. package/packages/recipe-kit-stencil/src/components/rk-modal/rk-modal.tsx +0 -176
  164. package/packages/recipe-kit-stencil/src/components/rk-rating/readme.md +0 -42
  165. package/packages/recipe-kit-stencil/src/components/rk-rating/rk-rating.tsx +0 -114
  166. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/readme.md +0 -66
  167. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/rk-recipe-card.tsx +0 -179
  168. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/test/rk-recipe-card.spec.tsx +0 -70
  169. package/packages/recipe-kit-stencil/src/components/rk-search-bar/readme.md +0 -67
  170. package/packages/recipe-kit-stencil/src/components/rk-search-bar/rk-search-bar.tsx +0 -152
  171. package/packages/recipe-kit-stencil/src/components/rk-search-bar/test/rk-search-bar.spec.tsx +0 -93
  172. package/packages/recipe-kit-stencil/src/components/rk-segmented-control/readme.md +0 -41
  173. package/packages/recipe-kit-stencil/src/components/rk-segmented-control/rk-segmented-control.tsx +0 -115
  174. package/packages/recipe-kit-stencil/src/components/rk-tag-input/readme.md +0 -47
  175. package/packages/recipe-kit-stencil/src/components/rk-tag-input/rk-tag-input.tsx +0 -181
  176. package/packages/recipe-kit-stencil/src/components/rk-tag-input/test/rk-tag-input.spec.tsx +0 -76
  177. package/packages/recipe-kit-stencil/src/index.html +0 -259
  178. package/packages/recipe-kit-stencil/stencil.config.ts +0 -41
  179. package/packages/recipe-kit-stencil/tsconfig.json +0 -24
  180. /package/{packages/recipe-kit-stencil/LICENSE → LICENSE} +0 -0
  181. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-badge/rk-badge.css +0 -0
  182. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-chip/rk-chip.css +0 -0
  183. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-empty-state/rk-empty-state.css +0 -0
  184. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-meal-slot/rk-meal-slot.css +0 -0
  185. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-modal/rk-modal.css +0 -0
  186. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-rating/rk-rating.css +0 -0
  187. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-recipe-card/rk-recipe-card.css +0 -0
  188. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-search-bar/rk-search-bar.css +0 -0
  189. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-segmented-control/rk-segmented-control.css +0 -0
  190. /package/{packages/recipe-kit-stencil/src → dist/collection}/components/rk-tag-input/rk-tag-input.css +0 -0
  191. /package/{packages/recipe-kit-stencil/src/global → dist/collection}/rk-global.css +0 -0
package/README.md CHANGED
@@ -1,354 +1,185 @@
1
- # Recipe Finder & Meal Planner
1
+ # @ssejal8/recipe-ui-kit
2
2
 
3
- A recipe discovery and weekly meal-planning application built with **Svelte 5 + SvelteKit**, using a **StencilJS** web component library that is published to npm and consumed as a package dependency.
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/ npm workspaces root
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
- ## Setup
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
- No environment variables and no API key are needed — TheMealDB's public test key is used.
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
- ```bash
88
- npm run dev
20
+ defineCustomElements();
89
21
  ```
90
22
 
91
- Builds the component library, then starts the app on **http://localhost:5173**.
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
- Other useful commands, all from the repository root:
25
+ ### 2. Load the design tokens
94
26
 
95
- | Command | What it does |
96
- | --- | --- |
97
- | `npm run dev` | Build the library, then run the app dev server |
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
- Working on a component and the app at the same time? Run `npm run dev:kit` and `npm run dev:app` in two terminals Stencil rebuilds `dist/` on save and Vite picks it up.
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
- To verify a real production server locally:
33
+ ### 3. Use the elements
108
34
 
109
- ```bash
110
- npm run build:kit
111
- npm run preview:node --workspace recipe-web # builds with adapter-node and serves it
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
- `recipe-kit-stencil` ships 10 framework-agnostic custom elements. Full prop / event / slot tables are in [`packages/recipe-kit-stencil/README.md`](./packages/recipe-kit-stencil/README.md) and in the generated docs beside each component.
51
+ Custom elements are a browser API, so registration must happen on the client only:
119
52
 
120
- | Tag | Role in the app | Events consumed by SvelteKit |
121
- | --- | --- | --- |
122
- | `rk-recipe-card` | Every recipe tile, in all grids | `rkCardSelect`, `rkFavoriteToggle` |
123
- | `rk-search-bar` | Discover, favorites, my recipes, picker | `rkSearch`, `rkClear` |
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
- ```bash
136
- npm run dev:kit # http://localhost:3333
58
+ onMount(async () => {
59
+ const { defineCustomElements } = await import('@ssejal8/recipe-ui-kit/loader');
60
+ await defineCustomElements();
61
+ });
62
+ </script>
137
63
  ```
138
64
 
139
- ### Design principles
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
- ## Publishing to npm
69
+ ## Components
149
70
 
150
- The library is configured to publish to the public registry regardless of any local registry setting, via `publishConfig` in its `package.json`.
151
-
152
- ```bash
153
- # 1. Log in to the public npm registry (once per machine)
154
- npm login --registry=https://registry.npmjs.org/
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
- # 2. Publish from the repository root
157
- npm run publish:kit
158
- ```
86
+ ---
159
87
 
160
- `prepublishOnly` runs a clean production build first, so a published tarball can never contain stale output. `files` is restricted to `dist/`, `loader/`, `README.md` and `LICENSE` — no source, no tests, no config.
88
+ ## Design principles
161
89
 
162
- ### Versioning
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
- [Semantic versioning](https://semver.org/), from the library directory:
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
- | Change | Command |
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
- Each script bumps the version, then publishes. After a bump, raise the range in `apps/recipe-web/package.json` to match.
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
- ## How SvelteKit consumes the package
100
+ ## Theming
177
101
 
178
- The app depends on the library **by package name**, exactly as any external consumer would:
102
+ Override any token on `:root` or on any ancestor, to scope a theme to a subtree:
179
103
 
180
- ```jsonc
181
- // apps/recipe-web/package.json
182
- "dependencies": {
183
- "recipe-kit-stencil": "^0.1.0"
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
- Nothing is imported from `packages/recipe-kit-stencil/src`. All imports resolve through the package's `exports` map to its built `dist/` and `loader/` output:
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
- Attributes can only carry strings, so array props such as `rk-segmented-control.options` must be assigned as DOM properties. `src/lib/stencil/WebComponent.svelte` is a small bridge that handles this for the whole app:
114
+ For finer control, target the exposed shadow parts:
207
115
 
208
- ```svelte
209
- <WebComponent
210
- tag="rk-recipe-card"
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
- ## Architecture
124
+ ## Events
249
125
 
250
- ### Data flow
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
- ### Validation
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
- ## Deployment
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
- ## Repository
142
+ ## Local development
310
143
 
311
144
  ```bash
312
- git remote add origin https://github.com/<your-username>/recipe-ui.git
313
- git push -u origin main
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
- Then fill in the repository and live-application links in the table at the top of this file.
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
- ## Assumptions and decisions
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
- **No backend, and user data is per-browser.** The assignment scopes the deliverable to the frontend, so favorites, user recipes and meal plans are stored in `localStorage`. They are therefore per-browser and per-device: they do not sync, and clearing site data clears them. A URL to a user-authored recipe only resolves in the browser that created it, which the detail page states explicitly rather than showing a bare 404.
325
-
326
- **Cook time and servings are only shown when they are real.** Rather than estimating a time for API recipes from instruction length, those fields are simply omitted for catalogue recipes and shown only for user-authored ones. Cards show ingredient count instead, which is genuine data.
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
- **The prep checklist is intentionally not persisted.** Ticking ingredients on the detail page is a cooking aid for the current session, not part of the recipe.
162
+ ```js
163
+ // Single-component import
164
+ import { RkRecipeCard } from '@ssejal8/recipe-ui-kit/components/rk-recipe-card';
165
+ ```
335
166
 
336
- **`recipe-kit-stencil` is unscoped.** The name was verified available on npm. Publishing under a scope instead only needs the `name` field changed in the library's `package.json` and the dependency in the app's.
167
+ ---
337
168
 
338
- **Node ≥ 20** is assumed, matching the SvelteKit 2 and Vite 8 baseline.
169
+ ## Versioning
339
170
 
340
- ---
171
+ [Semantic versioning](https://semver.org/):
341
172
 
342
- ## Known limitations
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
- - **Deploy and publish are not done.** Both need credentials this environment does not have. The npm package is built, packed and verified (`npm pack` produces a valid tarball); the production Node build is verified booting and serving. The two links at the top of this file are the remaining steps.
345
- - **Browser interaction was not verified end-to-end.** Every route was exercised over HTTP and asserted on its server-rendered output (card counts, filter intersection, planner grid, 404 handling), `svelte-check` reports zero errors, and 16 component spec tests pass. Click-through of the drag-and-drop planner and the create/edit form was not machine-verified.
346
- - **Stencil's bundled test runner is deprecated** as of Stencil 4 and will be removed in v5. The spec tests will need migrating to `@stencil/vitest`.
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