@ssejal8/recipe-ui-kit 0.1.0

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 (87) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.npmr +1 -0
  3. package/README.md +354 -0
  4. package/apps/recipe-web/package.json +31 -0
  5. package/apps/recipe-web/src/app.css +418 -0
  6. package/apps/recipe-web/src/app.d.ts +32 -0
  7. package/apps/recipe-web/src/app.html +25 -0
  8. package/apps/recipe-web/src/lib/api/mealdb.ts +240 -0
  9. package/apps/recipe-web/src/lib/components/AppHeader.svelte +290 -0
  10. package/apps/recipe-web/src/lib/components/ConfirmDialog.svelte +46 -0
  11. package/apps/recipe-web/src/lib/components/Modal.svelte +33 -0
  12. package/apps/recipe-web/src/lib/components/RecipeCard.svelte +100 -0
  13. package/apps/recipe-web/src/lib/components/RecipeForm.svelte +560 -0
  14. package/apps/recipe-web/src/lib/components/RecipeGrid.svelte +77 -0
  15. package/apps/recipe-web/src/lib/components/RecipePickerDialog.svelte +185 -0
  16. package/apps/recipe-web/src/lib/components/Toaster.svelte +111 -0
  17. package/apps/recipe-web/src/lib/domain/recipe.ts +130 -0
  18. package/apps/recipe-web/src/lib/domain/validation.ts +189 -0
  19. package/apps/recipe-web/src/lib/stencil/WebComponent.svelte +145 -0
  20. package/apps/recipe-web/src/lib/stencil/register.ts +28 -0
  21. package/apps/recipe-web/src/lib/stores/favorites.svelte.ts +81 -0
  22. package/apps/recipe-web/src/lib/stores/myRecipes.svelte.ts +98 -0
  23. package/apps/recipe-web/src/lib/stores/persisted.svelte.ts +73 -0
  24. package/apps/recipe-web/src/lib/stores/planner.svelte.ts +275 -0
  25. package/apps/recipe-web/src/lib/stores/theme.svelte.ts +67 -0
  26. package/apps/recipe-web/src/lib/stores/toast.svelte.ts +66 -0
  27. package/apps/recipe-web/src/routes/+error.svelte +36 -0
  28. package/apps/recipe-web/src/routes/+layout.svelte +62 -0
  29. package/apps/recipe-web/src/routes/+page.svelte +352 -0
  30. package/apps/recipe-web/src/routes/+page.ts +107 -0
  31. package/apps/recipe-web/src/routes/favorites/+page.svelte +170 -0
  32. package/apps/recipe-web/src/routes/my-recipes/+layout.ts +55 -0
  33. package/apps/recipe-web/src/routes/my-recipes/+page.svelte +180 -0
  34. package/apps/recipe-web/src/routes/my-recipes/[id]/edit/+page.svelte +104 -0
  35. package/apps/recipe-web/src/routes/my-recipes/new/+page.svelte +67 -0
  36. package/apps/recipe-web/src/routes/planner/+page.svelte +422 -0
  37. package/apps/recipe-web/src/routes/recipes/[id]/+page.svelte +538 -0
  38. package/apps/recipe-web/src/routes/recipes/[id]/+page.ts +24 -0
  39. package/apps/recipe-web/static/favicon.svg +8 -0
  40. package/apps/recipe-web/static/robots.txt +2 -0
  41. package/apps/recipe-web/svelte.config.js +35 -0
  42. package/apps/recipe-web/tsconfig.json +14 -0
  43. package/apps/recipe-web/vite.config.ts +23 -0
  44. package/package.json +29 -0
  45. package/packages/recipe-kit-stencil/LICENSE +21 -0
  46. package/packages/recipe-kit-stencil/README.md +185 -0
  47. package/packages/recipe-kit-stencil/package-lock.json +4772 -0
  48. package/packages/recipe-kit-stencil/package.json +91 -0
  49. package/packages/recipe-kit-stencil/src/components/rk-badge/readme.md +38 -0
  50. package/packages/recipe-kit-stencil/src/components/rk-badge/rk-badge.css +102 -0
  51. package/packages/recipe-kit-stencil/src/components/rk-badge/rk-badge.tsx +31 -0
  52. package/packages/recipe-kit-stencil/src/components/rk-chip/readme.md +52 -0
  53. package/packages/recipe-kit-stencil/src/components/rk-chip/rk-chip.css +155 -0
  54. package/packages/recipe-kit-stencil/src/components/rk-chip/rk-chip.tsx +105 -0
  55. package/packages/recipe-kit-stencil/src/components/rk-empty-state/readme.md +44 -0
  56. package/packages/recipe-kit-stencil/src/components/rk-empty-state/rk-empty-state.css +64 -0
  57. package/packages/recipe-kit-stencil/src/components/rk-empty-state/rk-empty-state.tsx +50 -0
  58. package/packages/recipe-kit-stencil/src/components/rk-meal-slot/readme.md +58 -0
  59. package/packages/recipe-kit-stencil/src/components/rk-meal-slot/rk-meal-slot.css +225 -0
  60. package/packages/recipe-kit-stencil/src/components/rk-meal-slot/rk-meal-slot.tsx +182 -0
  61. package/packages/recipe-kit-stencil/src/components/rk-modal/readme.md +57 -0
  62. package/packages/recipe-kit-stencil/src/components/rk-modal/rk-modal.css +163 -0
  63. package/packages/recipe-kit-stencil/src/components/rk-modal/rk-modal.tsx +176 -0
  64. package/packages/recipe-kit-stencil/src/components/rk-rating/readme.md +42 -0
  65. package/packages/recipe-kit-stencil/src/components/rk-rating/rk-rating.css +71 -0
  66. package/packages/recipe-kit-stencil/src/components/rk-rating/rk-rating.tsx +114 -0
  67. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/readme.md +66 -0
  68. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/rk-recipe-card.css +280 -0
  69. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/rk-recipe-card.tsx +179 -0
  70. package/packages/recipe-kit-stencil/src/components/rk-recipe-card/test/rk-recipe-card.spec.tsx +70 -0
  71. package/packages/recipe-kit-stencil/src/components/rk-search-bar/readme.md +67 -0
  72. package/packages/recipe-kit-stencil/src/components/rk-search-bar/rk-search-bar.css +141 -0
  73. package/packages/recipe-kit-stencil/src/components/rk-search-bar/rk-search-bar.tsx +152 -0
  74. package/packages/recipe-kit-stencil/src/components/rk-search-bar/test/rk-search-bar.spec.tsx +93 -0
  75. package/packages/recipe-kit-stencil/src/components/rk-segmented-control/readme.md +41 -0
  76. package/packages/recipe-kit-stencil/src/components/rk-segmented-control/rk-segmented-control.css +91 -0
  77. package/packages/recipe-kit-stencil/src/components/rk-segmented-control/rk-segmented-control.tsx +115 -0
  78. package/packages/recipe-kit-stencil/src/components/rk-tag-input/readme.md +47 -0
  79. package/packages/recipe-kit-stencil/src/components/rk-tag-input/rk-tag-input.css +146 -0
  80. package/packages/recipe-kit-stencil/src/components/rk-tag-input/rk-tag-input.tsx +181 -0
  81. package/packages/recipe-kit-stencil/src/components/rk-tag-input/test/rk-tag-input.spec.tsx +76 -0
  82. package/packages/recipe-kit-stencil/src/components.d.ts +1192 -0
  83. package/packages/recipe-kit-stencil/src/global/rk-global.css +104 -0
  84. package/packages/recipe-kit-stencil/src/index.html +259 -0
  85. package/packages/recipe-kit-stencil/src/index.ts +6 -0
  86. package/packages/recipe-kit-stencil/stencil.config.ts +41 -0
  87. package/packages/recipe-kit-stencil/tsconfig.json +24 -0
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "recipe-kit-stencil",
3
+ "version": "0.1.0",
4
+ "description": "Framework-agnostic web component library (StencilJS) for recipe discovery, favorites and meal planning UIs.",
5
+ "license": "MIT",
6
+ "author": "Sejal Sharma",
7
+ "keywords": [
8
+ "stencil",
9
+ "stenciljs",
10
+ "web-components",
11
+ "custom-elements",
12
+ "design-system",
13
+ "recipe",
14
+ "meal-planner",
15
+ "svelte",
16
+ "sveltekit"
17
+ ],
18
+ "type": "module",
19
+ "main": "dist/index.cjs.js",
20
+ "module": "dist/index.js",
21
+ "types": "dist/types/index.d.ts",
22
+ "collection": "dist/collection/collection-manifest.json",
23
+ "collection:main": "dist/collection/index.js",
24
+ "unpkg": "dist/recipe-kit/recipe-kit.esm.js",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/types/index.d.ts",
28
+ "import": "./dist/index.js",
29
+ "require": "./dist/index.cjs.js"
30
+ },
31
+ "./loader": {
32
+ "types": "./loader/index.d.ts",
33
+ "import": "./loader/index.js",
34
+ "require": "./loader/index.cjs.js"
35
+ },
36
+ "./components/*": {
37
+ "types": "./dist/components/*.d.ts",
38
+ "import": "./dist/components/*.js"
39
+ },
40
+ "./dist/recipe-kit/recipe-kit.css": "./dist/recipe-kit/recipe-kit.css",
41
+ "./styles": "./dist/recipe-kit/recipe-kit.css",
42
+ "./dist/*": "./dist/*",
43
+ "./package.json": "./package.json"
44
+ },
45
+ "files": [
46
+ "dist/",
47
+ "loader/",
48
+ "README.md",
49
+ "LICENSE"
50
+ ],
51
+ "sideEffects": [
52
+ "dist/**/*.css",
53
+ "dist/esm/polyfills/*"
54
+ ],
55
+ "publishConfig": {
56
+ "access": "public",
57
+ "registry": "https://registry.npmjs.org/"
58
+ },
59
+ "scripts": {
60
+ "build": "stencil build --docs",
61
+ "start": "stencil build --dev --watch --serve",
62
+ "test": "stencil test --spec",
63
+ "test:watch": "stencil test --spec --watchAll",
64
+ "generate": "stencil generate",
65
+ "clean": "rimraf dist loader www .stencil",
66
+ "prepare": "stencil build",
67
+ "prepublishOnly": "npm run clean && npm run build",
68
+ "release:patch": "npm version patch && npm publish",
69
+ "release:minor": "npm version minor && npm publish",
70
+ "release:major": "npm version major && npm publish"
71
+ },
72
+ "dependencies": {
73
+ "@stencil/core": "^4.44.1"
74
+ },
75
+ "devDependencies": {
76
+ "@types/jest": "^29.5.14",
77
+ "jest": "^29.7.0",
78
+ "jest-cli": "^29.7.0",
79
+ "puppeteer": "^24.10.2",
80
+ "rimraf": "^6.0.1"
81
+ },
82
+ "repository": {
83
+ "type": "git",
84
+ "url": "git+https://github.com/sejal-sharma/recipe-ui.git",
85
+ "directory": "packages/recipe-kit-stencil"
86
+ },
87
+ "bugs": {
88
+ "url": "https://github.com/sejal-sharma/recipe-ui/issues"
89
+ },
90
+ "homepage": "https://github.com/sejal-sharma/recipe-ui/tree/main/packages/recipe-kit-stencil#readme"
91
+ }
@@ -0,0 +1,38 @@
1
+ # rk-badge
2
+
3
+
4
+
5
+ <!-- Auto Generated Below -->
6
+
7
+
8
+ ## Overview
9
+
10
+ A small, non-interactive label. Purely slot-driven so hosts can put icons,
11
+ text or counts inside it.
12
+
13
+ ## Properties
14
+
15
+ | Property | Attribute | Description | Type | Default |
16
+ | --------- | --------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------- |
17
+ | `small` | `small` | Compact sizing for dense layouts. | `boolean` | `false` |
18
+ | `tone` | `tone` | Color treatment. | `"accent" \| "danger" \| "neutral" \| "primary" \| "success" \| "warning"` | `'neutral'` |
19
+ | `variant` | `variant` | `soft` is tinted, `solid` is filled, `outline` is bordered only. | `"outline" \| "soft" \| "solid"` | `'soft'` |
20
+
21
+
22
+ ## Slots
23
+
24
+ | Slot | Description |
25
+ | ---- | -------------- |
26
+ | | Badge content. |
27
+
28
+
29
+ ## Shadow Parts
30
+
31
+ | Part | Description |
32
+ | --------- | ----------- |
33
+ | `"badge"` | |
34
+
35
+
36
+ ----------------------------------------------
37
+
38
+ *Built with [StencilJS](https://stenciljs.com/)*
@@ -0,0 +1,102 @@
1
+ :host {
2
+ display: inline-flex;
3
+ font-family: var(--rk-font);
4
+ vertical-align: middle;
5
+ }
6
+
7
+ .badge {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ gap: 5px;
11
+ padding: 4px 10px;
12
+ border-radius: var(--rk-radius-pill, 999px);
13
+ border: 1px solid transparent;
14
+ font-size: 0.76em;
15
+ font-weight: 650;
16
+ line-height: 1.35;
17
+ letter-spacing: 0.01em;
18
+ white-space: nowrap;
19
+ }
20
+
21
+ .badge.is-small {
22
+ padding: 2px 8px;
23
+ font-size: 0.7em;
24
+ }
25
+
26
+ /* --- soft (default) --- */
27
+ .variant-soft.tone-neutral {
28
+ background: var(--rk-surface-2, #fdf5ef);
29
+ color: var(--rk-ink-2, #52443c);
30
+ }
31
+ .variant-soft.tone-primary {
32
+ background: var(--rk-primary-soft, #fdeee6);
33
+ color: var(--rk-primary-strong, #b23c11);
34
+ }
35
+ .variant-soft.tone-accent {
36
+ background: var(--rk-accent-soft, #e2f3f1);
37
+ color: var(--rk-accent, #0f766e);
38
+ }
39
+ .variant-soft.tone-success {
40
+ background: var(--rk-success-soft, #e6f5eb);
41
+ color: var(--rk-success, #15803d);
42
+ }
43
+ .variant-soft.tone-warning {
44
+ background: var(--rk-warning-soft, #fdf3d8);
45
+ color: var(--rk-warning, #a16207);
46
+ }
47
+ .variant-soft.tone-danger {
48
+ background: var(--rk-danger-soft, #fdeae1);
49
+ color: var(--rk-danger, #c2410c);
50
+ }
51
+
52
+ /* --- solid --- */
53
+ .variant-solid {
54
+ color: #fff;
55
+ }
56
+ .variant-solid.tone-neutral {
57
+ background: var(--rk-ink, #211a16);
58
+ }
59
+ .variant-solid.tone-primary {
60
+ background: var(--rk-primary, #d94f1b);
61
+ }
62
+ .variant-solid.tone-accent {
63
+ background: var(--rk-accent, #0f766e);
64
+ }
65
+ .variant-solid.tone-success {
66
+ background: var(--rk-success, #15803d);
67
+ }
68
+ .variant-solid.tone-warning {
69
+ background: var(--rk-warning, #a16207);
70
+ }
71
+ .variant-solid.tone-danger {
72
+ background: var(--rk-danger, #c2410c);
73
+ }
74
+
75
+ /* --- outline --- */
76
+ .variant-outline {
77
+ background: transparent;
78
+ }
79
+ .variant-outline.tone-neutral {
80
+ border-color: var(--rk-border-strong, #d9c6b8);
81
+ color: var(--rk-ink-2, #52443c);
82
+ }
83
+ .variant-outline.tone-primary {
84
+ border-color: var(--rk-primary, #d94f1b);
85
+ color: var(--rk-primary, #d94f1b);
86
+ }
87
+ .variant-outline.tone-accent {
88
+ border-color: var(--rk-accent, #0f766e);
89
+ color: var(--rk-accent, #0f766e);
90
+ }
91
+ .variant-outline.tone-success {
92
+ border-color: var(--rk-success, #15803d);
93
+ color: var(--rk-success, #15803d);
94
+ }
95
+ .variant-outline.tone-warning {
96
+ border-color: var(--rk-warning, #a16207);
97
+ color: var(--rk-warning, #a16207);
98
+ }
99
+ .variant-outline.tone-danger {
100
+ border-color: var(--rk-danger, #c2410c);
101
+ color: var(--rk-danger, #c2410c);
102
+ }
@@ -0,0 +1,31 @@
1
+ import { Component, h, Prop } from '@stencil/core';
2
+
3
+ /**
4
+ * A small, non-interactive label. Purely slot-driven so hosts can put icons,
5
+ * text or counts inside it.
6
+ *
7
+ * @slot - Badge content.
8
+ */
9
+ @Component({
10
+ tag: 'rk-badge',
11
+ styleUrl: 'rk-badge.css',
12
+ shadow: true,
13
+ })
14
+ export class RkBadge {
15
+ /** Color treatment. */
16
+ @Prop() tone: 'neutral' | 'primary' | 'accent' | 'success' | 'warning' | 'danger' = 'neutral';
17
+
18
+ /** `soft` is tinted, `solid` is filled, `outline` is bordered only. */
19
+ @Prop() variant: 'soft' | 'solid' | 'outline' = 'soft';
20
+
21
+ /** Compact sizing for dense layouts. */
22
+ @Prop() small = false;
23
+
24
+ render() {
25
+ return (
26
+ <span class={{ badge: true, [`tone-${this.tone}`]: true, [`variant-${this.variant}`]: true, 'is-small': this.small }} part="badge">
27
+ <slot />
28
+ </span>
29
+ );
30
+ }
31
+ }
@@ -0,0 +1,52 @@
1
+ # rk-chip
2
+
3
+
4
+
5
+ <!-- Auto Generated Below -->
6
+
7
+
8
+ ## Overview
9
+
10
+ A selectable / dismissible filter chip.
11
+
12
+ ## Properties
13
+
14
+ | Property | Attribute | Description | Type | Default |
15
+ | ------------- | ------------- | --------------------------------------------------------------------- | ------------------------------------ | ----------- |
16
+ | `count` | `count` | Optional trailing count badge (e.g. number of matching recipes). | `number` | `undefined` |
17
+ | `disabled` | `disabled` | Disables interaction. | `boolean` | `false` |
18
+ | `dismissible` | `dismissible` | Renders an "x" button that emits `rkChipDismiss` instead of toggling. | `boolean` | `false` |
19
+ | `label` | `label` | Visible text. | `string` | `''` |
20
+ | `selected` | `selected` | Whether the chip is currently active. | `boolean` | `false` |
21
+ | `tone` | `tone` | Visual treatment. | `"accent" \| "neutral" \| "primary"` | `'neutral'` |
22
+ | `value` | `value` | Value reported back to the host application in event payloads. | `string` | `undefined` |
23
+
24
+
25
+ ## Events
26
+
27
+ | Event | Description | Type |
28
+ | --------------- | ----------------------------------------------------------- | ------------------------------------------------------------------- |
29
+ | `rkChipDismiss` | Emitted when a dismissible chip's remove button is pressed. | `CustomEvent<{ value: string; label: string; }>` |
30
+ | `rkChipToggle` | Emitted when the chip is toggled. | `CustomEvent<{ value: string; label: string; selected: boolean; }>` |
31
+
32
+
33
+ ## Slots
34
+
35
+ | Slot | Description |
36
+ | ---- | ------------------------------------------------------------------------- |
37
+ | | Optional leading content (icon, emoji, avatar) rendered before the label. |
38
+
39
+
40
+ ## Shadow Parts
41
+
42
+ | Part | Description |
43
+ | ----------- | ----------- |
44
+ | `"chip"` | |
45
+ | `"count"` | |
46
+ | `"dismiss"` | |
47
+ | `"label"` | |
48
+
49
+
50
+ ----------------------------------------------
51
+
52
+ *Built with [StencilJS](https://stenciljs.com/)*
@@ -0,0 +1,155 @@
1
+ :host {
2
+ display: inline-flex;
3
+ font-family: var(--rk-font);
4
+ vertical-align: middle;
5
+ }
6
+
7
+ .chip,
8
+ .body {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: 7px;
12
+ font: inherit;
13
+ font-size: 0.88em;
14
+ font-weight: 550;
15
+ line-height: 1;
16
+ color: var(--rk-ink-2, #52443c);
17
+ background: var(--rk-surface, #fff);
18
+ border: 1px solid var(--rk-border, #ecdfd4);
19
+ border-radius: var(--rk-radius-pill, 999px);
20
+ padding: 9px 15px;
21
+ cursor: pointer;
22
+ white-space: nowrap;
23
+ transition: background var(--rk-transition), border-color var(--rk-transition), color var(--rk-transition), transform var(--rk-transition);
24
+ }
25
+
26
+ .chip:hover:not(:disabled),
27
+ .body:hover:not(:disabled) {
28
+ border-color: var(--rk-border-strong, #d9c6b8);
29
+ background: var(--rk-surface-2, #fdf5ef);
30
+ }
31
+
32
+ .chip:active:not(:disabled) {
33
+ transform: translateY(1px);
34
+ }
35
+
36
+ .chip:focus-visible,
37
+ .body:focus-visible,
38
+ .dismiss:focus-visible {
39
+ outline: none;
40
+ box-shadow: var(--rk-focus-ring);
41
+ }
42
+
43
+ .chip:disabled,
44
+ .body:disabled {
45
+ opacity: 0.5;
46
+ cursor: not-allowed;
47
+ }
48
+
49
+ /* Selected states, per tone */
50
+ .chip.is-selected.tone-neutral {
51
+ background: var(--rk-ink, #211a16);
52
+ border-color: var(--rk-ink, #211a16);
53
+ color: var(--rk-surface, #fff);
54
+ }
55
+
56
+ .chip.is-selected.tone-primary {
57
+ background: var(--rk-primary, #d94f1b);
58
+ border-color: var(--rk-primary, #d94f1b);
59
+ color: #fff;
60
+ }
61
+
62
+ .chip.is-selected.tone-accent {
63
+ background: var(--rk-accent, #0f766e);
64
+ border-color: var(--rk-accent, #0f766e);
65
+ color: #fff;
66
+ }
67
+
68
+ .chip.is-selected:hover:not(:disabled) {
69
+ filter: brightness(1.08);
70
+ }
71
+
72
+ .count {
73
+ display: inline-flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ min-width: 18px;
77
+ height: 18px;
78
+ padding: 0 5px;
79
+ border-radius: var(--rk-radius-pill, 999px);
80
+ background: var(--rk-surface-2, #fdf5ef);
81
+ color: var(--rk-muted, #857166);
82
+ font-size: 0.78em;
83
+ font-weight: 650;
84
+ }
85
+
86
+ .chip.is-selected .count {
87
+ background: rgba(255, 255, 255, 0.22);
88
+ color: inherit;
89
+ }
90
+
91
+ /*
92
+ * `display: contents` lets slotted nodes become direct flex items of the chip,
93
+ * so an *empty* leading slot contributes no box and therefore no stray gap.
94
+ */
95
+ .lead {
96
+ display: contents;
97
+ }
98
+
99
+ .lead ::slotted(*) {
100
+ font-size: 1.05em;
101
+ line-height: 1;
102
+ }
103
+
104
+ /* Dismissible layout: shell holds two buttons that read as one pill. */
105
+ .chip.is-dismissible {
106
+ padding: 0;
107
+ background: transparent;
108
+ border: 0;
109
+ border-radius: var(--rk-radius-pill, 999px);
110
+ gap: 0;
111
+ }
112
+
113
+ .chip.is-dismissible .body {
114
+ border-top-right-radius: 0;
115
+ border-bottom-right-radius: 0;
116
+ border-right: 0;
117
+ }
118
+
119
+ .dismiss {
120
+ display: inline-flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ width: 32px;
124
+ padding: 0;
125
+ border: 1px solid var(--rk-border, #ecdfd4);
126
+ border-left: 0;
127
+ border-radius: 0 var(--rk-radius-pill, 999px) var(--rk-radius-pill, 999px) 0;
128
+ background: var(--rk-surface, #fff);
129
+ color: var(--rk-muted, #857166);
130
+ cursor: pointer;
131
+ transition: background var(--rk-transition), color var(--rk-transition);
132
+ }
133
+
134
+ .dismiss:hover:not(:disabled) {
135
+ background: var(--rk-danger-soft, #fdeae1);
136
+ color: var(--rk-danger, #c2410c);
137
+ }
138
+
139
+ .dismiss:disabled {
140
+ opacity: 0.5;
141
+ cursor: not-allowed;
142
+ }
143
+
144
+ .dismiss svg {
145
+ width: 12px;
146
+ height: 12px;
147
+ }
148
+
149
+ @media (prefers-reduced-motion: reduce) {
150
+ .chip,
151
+ .body,
152
+ .dismiss {
153
+ transition: none;
154
+ }
155
+ }
@@ -0,0 +1,105 @@
1
+ import { Component, Event, EventEmitter, h, Prop } from '@stencil/core';
2
+
3
+ /**
4
+ * A selectable / dismissible filter chip.
5
+ *
6
+ * @slot - Optional leading content (icon, emoji, avatar) rendered before the label.
7
+ */
8
+ @Component({
9
+ tag: 'rk-chip',
10
+ styleUrl: 'rk-chip.css',
11
+ shadow: true,
12
+ })
13
+ export class RkChip {
14
+ /** Visible text. */
15
+ @Prop() label = '';
16
+
17
+ /** Value reported back to the host application in event payloads. */
18
+ @Prop() value?: string;
19
+
20
+ /** Whether the chip is currently active. */
21
+ @Prop() selected = false;
22
+
23
+ /** Optional trailing count badge (e.g. number of matching recipes). */
24
+ @Prop() count?: number;
25
+
26
+ /** Renders an "x" button that emits `rkChipDismiss` instead of toggling. */
27
+ @Prop() dismissible = false;
28
+
29
+ /** Disables interaction. */
30
+ @Prop() disabled = false;
31
+
32
+ /** Visual treatment. */
33
+ @Prop() tone: 'neutral' | 'primary' | 'accent' = 'neutral';
34
+
35
+ /** Emitted when the chip is toggled. */
36
+ @Event({ eventName: 'rkChipToggle' }) rkChipToggle!: EventEmitter<{ value: string; label: string; selected: boolean }>;
37
+
38
+ /** Emitted when a dismissible chip's remove button is pressed. */
39
+ @Event({ eventName: 'rkChipDismiss' }) rkChipDismiss!: EventEmitter<{ value: string; label: string }>;
40
+
41
+ private get resolvedValue() {
42
+ return this.value ?? this.label;
43
+ }
44
+
45
+ private handleClick = () => {
46
+ if (this.disabled) return;
47
+ this.rkChipToggle.emit({
48
+ value: this.resolvedValue,
49
+ label: this.label,
50
+ selected: !this.selected,
51
+ });
52
+ };
53
+
54
+ private handleDismiss = (event: MouseEvent) => {
55
+ event.stopPropagation();
56
+ if (this.disabled) return;
57
+ this.rkChipDismiss.emit({ value: this.resolvedValue, label: this.label });
58
+ };
59
+
60
+ render() {
61
+ const body = [
62
+ <span class="lead">
63
+ <slot />
64
+ </span>,
65
+ <span class="label" part="label">
66
+ {this.label}
67
+ </span>,
68
+ typeof this.count === 'number' && (
69
+ <span class="count" part="count">
70
+ {this.count}
71
+ </span>
72
+ ),
73
+ ];
74
+
75
+ const classes = {
76
+ chip: true,
77
+ 'is-selected': this.selected,
78
+ 'is-dismissible': this.dismissible,
79
+ [`tone-${this.tone}`]: true,
80
+ };
81
+
82
+ // A dismissible chip needs two independent controls, so the shell becomes a
83
+ // plain element to avoid nesting buttons inside a button.
84
+ if (this.dismissible) {
85
+ return (
86
+ <span class={classes} part="chip">
87
+ <button class="body" type="button" disabled={this.disabled} onClick={this.handleClick}>
88
+ {body}
89
+ </button>
90
+ <button class="dismiss" part="dismiss" type="button" disabled={this.disabled} aria-label={`Remove ${this.label}`} onClick={this.handleDismiss}>
91
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true">
92
+ <path d="M6 6l12 12M18 6 6 18" />
93
+ </svg>
94
+ </button>
95
+ </span>
96
+ );
97
+ }
98
+
99
+ return (
100
+ <button class={classes} part="chip" type="button" aria-pressed={String(this.selected)} disabled={this.disabled} onClick={this.handleClick}>
101
+ {body}
102
+ </button>
103
+ );
104
+ }
105
+ }
@@ -0,0 +1,44 @@
1
+ # rk-empty-state
2
+
3
+
4
+
5
+ <!-- Auto Generated Below -->
6
+
7
+
8
+ ## Overview
9
+
10
+ Placeholder shown when a list has no items, a search returns nothing, or a
11
+ request fails.
12
+
13
+ ## Properties
14
+
15
+ | Property | Attribute | Description | Type | Default |
16
+ | --------- | --------- | -------------------------------------------------------------- | ---------------------- | -------------------- |
17
+ | `bare` | `bare` | Removes the dashed border and reduces padding, for inline use. | `boolean` | `false` |
18
+ | `heading` | `heading` | Bold headline. | `string` | `'Nothing here yet'` |
19
+ | `icon` | `icon` | Emoji or short glyph shown above the heading. | `string` | `'🍳'` |
20
+ | `message` | `message` | Supporting sentence. | `string` | `undefined` |
21
+ | `tone` | `tone` | `error` swaps to a warning treatment. | `"error" \| "neutral"` | `'neutral'` |
22
+
23
+
24
+ ## Slots
25
+
26
+ | Slot | Description |
27
+ | ---- | ------------------------------------------------------------------- |
28
+ | | Call-to-action content rendered under the message (buttons, links). |
29
+
30
+
31
+ ## Shadow Parts
32
+
33
+ | Part | Description |
34
+ | ------------- | ----------- |
35
+ | `"actions"` | |
36
+ | `"container"` | |
37
+ | `"heading"` | |
38
+ | `"icon"` | |
39
+ | `"message"` | |
40
+
41
+
42
+ ----------------------------------------------
43
+
44
+ *Built with [StencilJS](https://stenciljs.com/)*
@@ -0,0 +1,64 @@
1
+ :host {
2
+ display: block;
3
+ font-family: var(--rk-font);
4
+ font-size: var(--rk-font-size, 15px);
5
+ }
6
+
7
+ .empty {
8
+ display: flex;
9
+ flex-direction: column;
10
+ align-items: center;
11
+ text-align: center;
12
+ gap: 8px;
13
+ padding: 52px 28px;
14
+ border: 1.5px dashed var(--rk-border-strong, #d9c6b8);
15
+ border-radius: var(--rk-radius-lg, 20px);
16
+ background: var(--rk-surface-2, #fdf5ef);
17
+ color: var(--rk-ink, #211a16);
18
+ }
19
+
20
+ .empty.is-bare {
21
+ border: 0;
22
+ background: transparent;
23
+ padding: 28px 16px;
24
+ }
25
+
26
+ .empty.tone-error {
27
+ border-color: var(--rk-danger, #c2410c);
28
+ background: var(--rk-danger-soft, #fdeae1);
29
+ }
30
+
31
+ .icon {
32
+ font-size: 2.5rem;
33
+ line-height: 1;
34
+ margin-bottom: 4px;
35
+ }
36
+
37
+ .heading {
38
+ margin: 0;
39
+ font-family: var(--rk-font-display, serif);
40
+ font-size: 1.25em;
41
+ font-weight: 650;
42
+ letter-spacing: -0.01em;
43
+ }
44
+
45
+ .message {
46
+ margin: 0;
47
+ max-width: 46ch;
48
+ color: var(--rk-muted, #857166);
49
+ line-height: 1.55;
50
+ font-size: 0.94em;
51
+ }
52
+
53
+ .empty.tone-error .message {
54
+ color: var(--rk-danger, #c2410c);
55
+ }
56
+
57
+ /* `display: contents` keeps an unused actions slot from adding vertical space. */
58
+ .actions {
59
+ display: contents;
60
+ }
61
+
62
+ .actions ::slotted(*) {
63
+ margin-top: 12px;
64
+ }