recipe-planner-ui 1.0.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.
- package/LICENSE +21 -0
- package/dist/cjs/app-globals-V2Kpy_OQ.js +5 -0
- package/dist/cjs/index-B6uIqnrk.js +2043 -0
- package/dist/cjs/index.cjs.js +2 -0
- package/dist/cjs/loader.cjs.js +13 -0
- package/dist/cjs/recipe-planner-ui.cjs.js +25 -0
- package/dist/cjs/rp-day-slot.cjs.entry.js +57 -0
- package/dist/cjs/rp-filter-chips_5.cjs.entry.js +240 -0
- package/dist/collection/collection-manifest.json +18 -0
- package/dist/collection/components/rp-day-slot/rp-day-slot.css +131 -0
- package/dist/collection/components/rp-day-slot/rp-day-slot.js +201 -0
- package/dist/collection/components/rp-filter-chips/rp-filter-chips.css +36 -0
- package/dist/collection/components/rp-filter-chips/rp-filter-chips.js +123 -0
- package/dist/collection/components/rp-ingredient-list/rp-ingredient-list.css +53 -0
- package/dist/collection/components/rp-ingredient-list/rp-ingredient-list.js +57 -0
- package/dist/collection/components/rp-modal/rp-modal.css +94 -0
- package/dist/collection/components/rp-modal/rp-modal.js +196 -0
- package/dist/collection/components/rp-recipe-card/rp-recipe-card.css +106 -0
- package/dist/collection/components/rp-recipe-card/rp-recipe-card.js +170 -0
- package/dist/collection/components/rp-search-bar/rp-search-bar.css +65 -0
- package/dist/collection/components/rp-search-bar/rp-search-bar.js +166 -0
- package/dist/collection/index.js +1 -0
- package/dist/components/index.d.ts +35 -0
- package/dist/components/index.js +1 -0
- package/dist/components/rp-day-slot.d.ts +11 -0
- package/dist/components/rp-day-slot.js +1 -0
- package/dist/components/rp-filter-chips.d.ts +11 -0
- package/dist/components/rp-filter-chips.js +1 -0
- package/dist/components/rp-ingredient-list.d.ts +11 -0
- package/dist/components/rp-ingredient-list.js +1 -0
- package/dist/components/rp-modal.d.ts +11 -0
- package/dist/components/rp-modal.js +1 -0
- package/dist/components/rp-recipe-card.d.ts +11 -0
- package/dist/components/rp-recipe-card.js +1 -0
- package/dist/components/rp-search-bar.d.ts +11 -0
- package/dist/components/rp-search-bar.js +1 -0
- package/dist/esm/app-globals-DQuL1Twl.js +3 -0
- package/dist/esm/index-B4wAFfci.js +2034 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/loader.js +11 -0
- package/dist/esm/recipe-planner-ui.js +21 -0
- package/dist/esm/rp-day-slot.entry.js +55 -0
- package/dist/esm/rp-filter-chips_5.entry.js +234 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.js +1 -0
- package/dist/recipe-planner-ui/index.esm.js +0 -0
- package/dist/recipe-planner-ui/p-66ba2983.entry.js +1 -0
- package/dist/recipe-planner-ui/p-B4wAFfci.js +3 -0
- package/dist/recipe-planner-ui/p-DQuL1Twl.js +1 -0
- package/dist/recipe-planner-ui/p-d54dbd37.entry.js +1 -0
- package/dist/recipe-planner-ui/recipe-planner-ui.css +1 -0
- package/dist/recipe-planner-ui/recipe-planner-ui.esm.js +1 -0
- package/dist/types/components/rp-day-slot/rp-day-slot.d.ts +41 -0
- package/dist/types/components/rp-filter-chips/rp-filter-chips.d.ts +24 -0
- package/dist/types/components/rp-ingredient-list/rp-ingredient-list.d.ts +14 -0
- package/dist/types/components/rp-modal/rp-modal.d.ts +34 -0
- package/dist/types/components/rp-recipe-card/rp-recipe-card.d.ts +36 -0
- package/dist/types/components/rp-search-bar/rp-search-bar.d.ts +31 -0
- package/dist/types/components.d.ts +536 -0
- package/dist/types/index.d.ts +7 -0
- 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 +72 -0
- package/readme.md +131 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Host, h } from "@stencil/core";
|
|
2
|
+
/**
|
|
3
|
+
* One day column of a weekly meal plan.
|
|
4
|
+
*
|
|
5
|
+
* Moving a meal is a select rather than drag-and-drop: it works with a keyboard and on
|
|
6
|
+
* touch without a fallback path, and the plan is small enough that dragging adds nothing.
|
|
7
|
+
*
|
|
8
|
+
* @slot - Empty-state content shown when the day has no meals.
|
|
9
|
+
*/
|
|
10
|
+
export class DaySlot {
|
|
11
|
+
/** Machine-readable day key, echoed in every event. */
|
|
12
|
+
day;
|
|
13
|
+
/** Human-readable day name shown as the column heading. */
|
|
14
|
+
dayLabel = '';
|
|
15
|
+
/** Meals planned for this day. Must be assigned as a DOM property. */
|
|
16
|
+
meals = [];
|
|
17
|
+
/** All day keys, used to populate each meal's move target list. */
|
|
18
|
+
days = [];
|
|
19
|
+
normalized = [];
|
|
20
|
+
/** Fired when the user asks to add a meal to this day. */
|
|
21
|
+
rpAddMeal;
|
|
22
|
+
/** Fired when a meal is removed from this day. */
|
|
23
|
+
rpRemoveMeal;
|
|
24
|
+
/** Fired when a meal is reassigned to a different day. */
|
|
25
|
+
rpMoveMeal;
|
|
26
|
+
componentWillLoad() {
|
|
27
|
+
this.normalized = Array.isArray(this.meals) ? this.meals : [];
|
|
28
|
+
}
|
|
29
|
+
componentWillUpdate() {
|
|
30
|
+
// Recomputed before each render rather than in a @Watch, because the property can be
|
|
31
|
+
// replaced before the element upgrades, which @Watch would miss.
|
|
32
|
+
this.normalized = Array.isArray(this.meals) ? this.meals : [];
|
|
33
|
+
}
|
|
34
|
+
onMove = (event, meal) => {
|
|
35
|
+
const select = event.target;
|
|
36
|
+
const to = select.value;
|
|
37
|
+
if (!to || to === this.day)
|
|
38
|
+
return;
|
|
39
|
+
this.rpMoveMeal.emit({ id: meal.id, from: this.day, to });
|
|
40
|
+
// The consumer re-renders from its own data; resetting avoids a stale selection
|
|
41
|
+
// if that render is delayed.
|
|
42
|
+
select.value = '';
|
|
43
|
+
};
|
|
44
|
+
render() {
|
|
45
|
+
const days = Array.isArray(this.days) ? this.days : [];
|
|
46
|
+
const meals = this.normalized;
|
|
47
|
+
return (h(Host, { key: '689cfc71ca7fc7106e29278d027ce1c36b3830c0' }, h("section", { key: 'd15ed9ae6f800be9962c4c1258397ff1043fef76', class: "slot" }, h("header", { key: '7f3ce1dcd4e94af5699b378ab1e461a34792acd8', class: "head" }, h("h3", { key: 'b62fe42a27f0072370e59a101452f77de8527ae0', class: "day" }, this.dayLabel || this.day), h("button", { key: '658191328f041bc2dd7534a3f5614746f26c0f28', type: "button", class: "add", onClick: () => this.rpAddMeal.emit(this.day), "aria-label": `Add a meal to ${this.dayLabel || this.day}` }, "+")), meals.length === 0 ? (h("div", { class: "empty" }, h("slot", null, h("p", { class: "empty-text" }, "No meals planned")))) : (h("ul", { class: "meals" }, meals.map((meal) => (h("li", { class: "meal", key: meal.id }, h("span", { class: "meal-title" }, meal.title), h("div", { class: "meal-actions" }, h("label", { class: "move" }, h("span", { class: "sr-only" }, `Move ${meal.title} to another day`), h("select", { class: "move-select", onChange: (event) => this.onMove(event, meal) }, h("option", { value: "" }, "Move\u2026"), days
|
|
48
|
+
.filter((day) => day !== this.day)
|
|
49
|
+
.map((day) => (h("option", { key: day, value: day }, day))))), h("button", { type: "button", class: "remove", onClick: () => this.rpRemoveMeal.emit({ id: meal.id, day: this.day }), "aria-label": `Remove ${meal.title}` }, "\u00D7"))))))))));
|
|
50
|
+
}
|
|
51
|
+
static get is() { return "rp-day-slot"; }
|
|
52
|
+
static get encapsulation() { return "scoped"; }
|
|
53
|
+
static get originalStyleUrls() {
|
|
54
|
+
return {
|
|
55
|
+
"$": ["rp-day-slot.css"]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
static get styleUrls() {
|
|
59
|
+
return {
|
|
60
|
+
"$": ["rp-day-slot.css"]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
static get properties() {
|
|
64
|
+
return {
|
|
65
|
+
"day": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"mutable": false,
|
|
68
|
+
"complexType": {
|
|
69
|
+
"original": "string",
|
|
70
|
+
"resolved": "string",
|
|
71
|
+
"references": {}
|
|
72
|
+
},
|
|
73
|
+
"required": true,
|
|
74
|
+
"optional": false,
|
|
75
|
+
"docs": {
|
|
76
|
+
"tags": [],
|
|
77
|
+
"text": "Machine-readable day key, echoed in every event."
|
|
78
|
+
},
|
|
79
|
+
"getter": false,
|
|
80
|
+
"setter": false,
|
|
81
|
+
"reflect": false,
|
|
82
|
+
"attribute": "day"
|
|
83
|
+
},
|
|
84
|
+
"dayLabel": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"mutable": false,
|
|
87
|
+
"complexType": {
|
|
88
|
+
"original": "string",
|
|
89
|
+
"resolved": "string",
|
|
90
|
+
"references": {}
|
|
91
|
+
},
|
|
92
|
+
"required": false,
|
|
93
|
+
"optional": false,
|
|
94
|
+
"docs": {
|
|
95
|
+
"tags": [],
|
|
96
|
+
"text": "Human-readable day name shown as the column heading."
|
|
97
|
+
},
|
|
98
|
+
"getter": false,
|
|
99
|
+
"setter": false,
|
|
100
|
+
"reflect": false,
|
|
101
|
+
"attribute": "day-label",
|
|
102
|
+
"defaultValue": "''"
|
|
103
|
+
},
|
|
104
|
+
"meals": {
|
|
105
|
+
"type": "unknown",
|
|
106
|
+
"mutable": false,
|
|
107
|
+
"complexType": {
|
|
108
|
+
"original": "PlannedMeal[]",
|
|
109
|
+
"resolved": "PlannedMeal[]",
|
|
110
|
+
"references": {
|
|
111
|
+
"PlannedMeal": {
|
|
112
|
+
"location": "local",
|
|
113
|
+
"path": "C:/Users/sonis/Meal Planner/recipe-ui/src/components/rp-day-slot/rp-day-slot.tsx",
|
|
114
|
+
"id": "src/components/rp-day-slot/rp-day-slot.tsx::PlannedMeal"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"required": false,
|
|
119
|
+
"optional": false,
|
|
120
|
+
"docs": {
|
|
121
|
+
"tags": [],
|
|
122
|
+
"text": "Meals planned for this day. Must be assigned as a DOM property."
|
|
123
|
+
},
|
|
124
|
+
"getter": false,
|
|
125
|
+
"setter": false,
|
|
126
|
+
"defaultValue": "[]"
|
|
127
|
+
},
|
|
128
|
+
"days": {
|
|
129
|
+
"type": "unknown",
|
|
130
|
+
"mutable": false,
|
|
131
|
+
"complexType": {
|
|
132
|
+
"original": "string[]",
|
|
133
|
+
"resolved": "string[]",
|
|
134
|
+
"references": {}
|
|
135
|
+
},
|
|
136
|
+
"required": false,
|
|
137
|
+
"optional": false,
|
|
138
|
+
"docs": {
|
|
139
|
+
"tags": [],
|
|
140
|
+
"text": "All day keys, used to populate each meal's move target list."
|
|
141
|
+
},
|
|
142
|
+
"getter": false,
|
|
143
|
+
"setter": false,
|
|
144
|
+
"defaultValue": "[]"
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
static get states() {
|
|
149
|
+
return {
|
|
150
|
+
"normalized": {}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
static get events() {
|
|
154
|
+
return [{
|
|
155
|
+
"method": "rpAddMeal",
|
|
156
|
+
"name": "rpAddMeal",
|
|
157
|
+
"bubbles": true,
|
|
158
|
+
"cancelable": true,
|
|
159
|
+
"composed": true,
|
|
160
|
+
"docs": {
|
|
161
|
+
"tags": [],
|
|
162
|
+
"text": "Fired when the user asks to add a meal to this day."
|
|
163
|
+
},
|
|
164
|
+
"complexType": {
|
|
165
|
+
"original": "string",
|
|
166
|
+
"resolved": "string",
|
|
167
|
+
"references": {}
|
|
168
|
+
}
|
|
169
|
+
}, {
|
|
170
|
+
"method": "rpRemoveMeal",
|
|
171
|
+
"name": "rpRemoveMeal",
|
|
172
|
+
"bubbles": true,
|
|
173
|
+
"cancelable": true,
|
|
174
|
+
"composed": true,
|
|
175
|
+
"docs": {
|
|
176
|
+
"tags": [],
|
|
177
|
+
"text": "Fired when a meal is removed from this day."
|
|
178
|
+
},
|
|
179
|
+
"complexType": {
|
|
180
|
+
"original": "{ id: string; day: string }",
|
|
181
|
+
"resolved": "{ id: string; day: string; }",
|
|
182
|
+
"references": {}
|
|
183
|
+
}
|
|
184
|
+
}, {
|
|
185
|
+
"method": "rpMoveMeal",
|
|
186
|
+
"name": "rpMoveMeal",
|
|
187
|
+
"bubbles": true,
|
|
188
|
+
"cancelable": true,
|
|
189
|
+
"composed": true,
|
|
190
|
+
"docs": {
|
|
191
|
+
"tags": [],
|
|
192
|
+
"text": "Fired when a meal is reassigned to a different day."
|
|
193
|
+
},
|
|
194
|
+
"complexType": {
|
|
195
|
+
"original": "{ id: string; from: string; to: string }",
|
|
196
|
+
"resolved": "{ id: string; from: string; to: string; }",
|
|
197
|
+
"references": {}
|
|
198
|
+
}
|
|
199
|
+
}];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
font-family: var(--rp-font-sans);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.chips {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-wrap: wrap;
|
|
9
|
+
gap: var(--rp-space-2);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.chip {
|
|
13
|
+
padding: var(--rp-space-1) var(--rp-space-3);
|
|
14
|
+
font: inherit;
|
|
15
|
+
font-size: var(--rp-font-size-sm);
|
|
16
|
+
color: var(--rp-color-text);
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
background: var(--rp-color-surface);
|
|
19
|
+
border: 1px solid var(--rp-color-border);
|
|
20
|
+
border-radius: 999px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.chip:hover {
|
|
24
|
+
background: var(--rp-color-surface-sunken);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.chip.is-selected {
|
|
28
|
+
color: var(--rp-color-accent-contrast);
|
|
29
|
+
background: var(--rp-color-accent);
|
|
30
|
+
border-color: var(--rp-color-accent);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.chip:focus-visible {
|
|
34
|
+
outline: var(--rp-focus-ring);
|
|
35
|
+
outline-offset: var(--rp-focus-offset);
|
|
36
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Host, h } from "@stencil/core";
|
|
2
|
+
/**
|
|
3
|
+
* A single-select row of filter chips.
|
|
4
|
+
*
|
|
5
|
+
* Selecting the active chip clears the filter, so the event detail is nullable —
|
|
6
|
+
* consumers translate null into "remove this search param".
|
|
7
|
+
*/
|
|
8
|
+
export class FilterChips {
|
|
9
|
+
/**
|
|
10
|
+
* Available filter values.
|
|
11
|
+
*
|
|
12
|
+
* An array has no attribute representation, so this must be assigned as a DOM
|
|
13
|
+
* property. Frameworks that set properties on custom elements do this automatically.
|
|
14
|
+
*/
|
|
15
|
+
options = [];
|
|
16
|
+
/** Currently active value, or null when no filter is applied. */
|
|
17
|
+
selected = null;
|
|
18
|
+
/** Accessible label for the group. */
|
|
19
|
+
label = 'Filter';
|
|
20
|
+
/** Fired with the new selection, or null when the filter is cleared. */
|
|
21
|
+
rpFilterChange;
|
|
22
|
+
select(option) {
|
|
23
|
+
this.rpFilterChange.emit(this.selected === option ? null : option);
|
|
24
|
+
}
|
|
25
|
+
render() {
|
|
26
|
+
// A string prop arriving from an HTML attribute would be unusable; tolerate it so
|
|
27
|
+
// the component behaves predictably in a plain .html page.
|
|
28
|
+
const options = Array.isArray(this.options) ? this.options : [];
|
|
29
|
+
return (h(Host, { key: '656b969af2e55a7c4313bc0c2c422e000811af53' }, h("div", { key: '1da1173fc1d6d10a312a43466a0ea799b625caef', class: "chips", role: "group", "aria-label": this.label }, options.map((option) => (h("button", { key: option, type: "button", class: { chip: true, 'is-selected': this.selected === option }, "aria-pressed": String(this.selected === option), onClick: () => this.select(option) }, option))))));
|
|
30
|
+
}
|
|
31
|
+
static get is() { return "rp-filter-chips"; }
|
|
32
|
+
static get encapsulation() { return "scoped"; }
|
|
33
|
+
static get originalStyleUrls() {
|
|
34
|
+
return {
|
|
35
|
+
"$": ["rp-filter-chips.css"]
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
static get styleUrls() {
|
|
39
|
+
return {
|
|
40
|
+
"$": ["rp-filter-chips.css"]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
static get properties() {
|
|
44
|
+
return {
|
|
45
|
+
"options": {
|
|
46
|
+
"type": "unknown",
|
|
47
|
+
"mutable": false,
|
|
48
|
+
"complexType": {
|
|
49
|
+
"original": "string[]",
|
|
50
|
+
"resolved": "string[]",
|
|
51
|
+
"references": {}
|
|
52
|
+
},
|
|
53
|
+
"required": false,
|
|
54
|
+
"optional": false,
|
|
55
|
+
"docs": {
|
|
56
|
+
"tags": [],
|
|
57
|
+
"text": "Available filter values.\n\nAn array has no attribute representation, so this must be assigned as a DOM\nproperty. Frameworks that set properties on custom elements do this automatically."
|
|
58
|
+
},
|
|
59
|
+
"getter": false,
|
|
60
|
+
"setter": false,
|
|
61
|
+
"defaultValue": "[]"
|
|
62
|
+
},
|
|
63
|
+
"selected": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"mutable": false,
|
|
66
|
+
"complexType": {
|
|
67
|
+
"original": "string | null",
|
|
68
|
+
"resolved": "string",
|
|
69
|
+
"references": {}
|
|
70
|
+
},
|
|
71
|
+
"required": false,
|
|
72
|
+
"optional": false,
|
|
73
|
+
"docs": {
|
|
74
|
+
"tags": [],
|
|
75
|
+
"text": "Currently active value, or null when no filter is applied."
|
|
76
|
+
},
|
|
77
|
+
"getter": false,
|
|
78
|
+
"setter": false,
|
|
79
|
+
"reflect": false,
|
|
80
|
+
"attribute": "selected",
|
|
81
|
+
"defaultValue": "null"
|
|
82
|
+
},
|
|
83
|
+
"label": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"mutable": false,
|
|
86
|
+
"complexType": {
|
|
87
|
+
"original": "string",
|
|
88
|
+
"resolved": "string",
|
|
89
|
+
"references": {}
|
|
90
|
+
},
|
|
91
|
+
"required": false,
|
|
92
|
+
"optional": false,
|
|
93
|
+
"docs": {
|
|
94
|
+
"tags": [],
|
|
95
|
+
"text": "Accessible label for the group."
|
|
96
|
+
},
|
|
97
|
+
"getter": false,
|
|
98
|
+
"setter": false,
|
|
99
|
+
"reflect": false,
|
|
100
|
+
"attribute": "label",
|
|
101
|
+
"defaultValue": "'Filter'"
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
static get events() {
|
|
106
|
+
return [{
|
|
107
|
+
"method": "rpFilterChange",
|
|
108
|
+
"name": "rpFilterChange",
|
|
109
|
+
"bubbles": true,
|
|
110
|
+
"cancelable": true,
|
|
111
|
+
"composed": true,
|
|
112
|
+
"docs": {
|
|
113
|
+
"tags": [],
|
|
114
|
+
"text": "Fired with the new selection, or null when the filter is cleared."
|
|
115
|
+
},
|
|
116
|
+
"complexType": {
|
|
117
|
+
"original": "string | null",
|
|
118
|
+
"resolved": "string",
|
|
119
|
+
"references": {}
|
|
120
|
+
}
|
|
121
|
+
}];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
font-family: var(--rp-font-sans);
|
|
4
|
+
color: var(--rp-color-text);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.heading {
|
|
8
|
+
margin: 0 0 var(--rp-space-3);
|
|
9
|
+
font-size: var(--rp-font-size-lg);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.list {
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
list-style: none;
|
|
16
|
+
border: 1px solid var(--rp-color-border);
|
|
17
|
+
border-radius: var(--rp-radius-md);
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.row {
|
|
22
|
+
display: flex;
|
|
23
|
+
justify-content: space-between;
|
|
24
|
+
gap: var(--rp-space-4);
|
|
25
|
+
padding: var(--rp-space-2) var(--rp-space-3);
|
|
26
|
+
font-size: var(--rp-font-size-md);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.row:nth-child(odd) {
|
|
30
|
+
background: var(--rp-color-surface-sunken);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.measure {
|
|
34
|
+
flex-shrink: 0;
|
|
35
|
+
color: var(--rp-color-text-muted);
|
|
36
|
+
font-variant-numeric: tabular-nums;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.empty {
|
|
40
|
+
margin: 0;
|
|
41
|
+
font-size: var(--rp-font-size-md);
|
|
42
|
+
color: var(--rp-color-text-muted);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.note {
|
|
46
|
+
margin-top: var(--rp-space-2);
|
|
47
|
+
font-size: var(--rp-font-size-sm);
|
|
48
|
+
color: var(--rp-color-text-muted);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.note:not(:has(*)) {
|
|
52
|
+
display: none;
|
|
53
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Host, h } from "@stencil/core";
|
|
2
|
+
/**
|
|
3
|
+
* A measure-and-name ingredient table.
|
|
4
|
+
*
|
|
5
|
+
* @slot heading - Replaces the default heading.
|
|
6
|
+
* @slot - Footnote content rendered beneath the list, such as a serving count.
|
|
7
|
+
*/
|
|
8
|
+
export class IngredientList {
|
|
9
|
+
/** Ingredients to display. Must be assigned as a DOM property, not an attribute. */
|
|
10
|
+
items = [];
|
|
11
|
+
render() {
|
|
12
|
+
const items = Array.isArray(this.items) ? this.items : [];
|
|
13
|
+
return (h(Host, { key: '22708ae6e60de472b2f8e40fb54b33a2ec691ad1' }, h("section", { key: '6f922fc37e3c230511bbdf72e55826dfa73e167e', class: "wrap" }, h("slot", { key: 'e9f0b16183f5ded825a5c1663caa54dcb6c2d961', name: "heading" }, h("h2", { key: 'c68ebf97056bc6309d3c4e79d1796d89eede1325', class: "heading" }, "Ingredients")), items.length === 0 ? (h("p", { class: "empty" }, "No ingredients listed for this recipe.")) : (h("ul", { class: "list" }, items.map((item, index) => (
|
|
14
|
+
// Ingredient names repeat within a recipe (for example, oil listed twice
|
|
15
|
+
// with different measures), so the index is part of the identity.
|
|
16
|
+
h("li", { class: "row", key: `${item.name}-${index}` }, h("span", { class: "name" }, item.name), h("span", { class: "measure" }, item.measure)))))), h("div", { key: '28d26e1bbb9f14e5dedfb153ff87e44add5cfbfb', class: "note" }, h("slot", { key: '20410e710e3c7dbc2ee9f6ad9493984382693687' })))));
|
|
17
|
+
}
|
|
18
|
+
static get is() { return "rp-ingredient-list"; }
|
|
19
|
+
static get encapsulation() { return "scoped"; }
|
|
20
|
+
static get originalStyleUrls() {
|
|
21
|
+
return {
|
|
22
|
+
"$": ["rp-ingredient-list.css"]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
static get styleUrls() {
|
|
26
|
+
return {
|
|
27
|
+
"$": ["rp-ingredient-list.css"]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
static get properties() {
|
|
31
|
+
return {
|
|
32
|
+
"items": {
|
|
33
|
+
"type": "unknown",
|
|
34
|
+
"mutable": false,
|
|
35
|
+
"complexType": {
|
|
36
|
+
"original": "Array<{ name: string; measure: string }>",
|
|
37
|
+
"resolved": "{ name: string; measure: string; }[]",
|
|
38
|
+
"references": {
|
|
39
|
+
"Array": {
|
|
40
|
+
"location": "global",
|
|
41
|
+
"id": "global::Array"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"required": false,
|
|
46
|
+
"optional": false,
|
|
47
|
+
"docs": {
|
|
48
|
+
"tags": [],
|
|
49
|
+
"text": "Ingredients to display. Must be assigned as a DOM property, not an attribute."
|
|
50
|
+
},
|
|
51
|
+
"getter": false,
|
|
52
|
+
"setter": false,
|
|
53
|
+
"defaultValue": "[]"
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: contents;
|
|
3
|
+
font-family: var(--rp-font-sans);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.backdrop {
|
|
7
|
+
position: fixed;
|
|
8
|
+
inset: 0;
|
|
9
|
+
z-index: 100;
|
|
10
|
+
display: grid;
|
|
11
|
+
place-items: center;
|
|
12
|
+
padding: var(--rp-space-4);
|
|
13
|
+
background: rgb(0 0 0 / 0.5);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.dialog {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
width: min(560px, 100%);
|
|
20
|
+
max-height: min(80vh, 640px);
|
|
21
|
+
color: var(--rp-color-text);
|
|
22
|
+
background: var(--rp-color-surface);
|
|
23
|
+
border-radius: var(--rp-radius-lg);
|
|
24
|
+
box-shadow: var(--rp-shadow-lg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.head {
|
|
28
|
+
display: flex;
|
|
29
|
+
gap: var(--rp-space-3);
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: space-between;
|
|
32
|
+
padding: var(--rp-space-4);
|
|
33
|
+
border-bottom: 1px solid var(--rp-color-border);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.heading {
|
|
37
|
+
margin: 0;
|
|
38
|
+
font-size: var(--rp-font-size-lg);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.close {
|
|
42
|
+
display: grid;
|
|
43
|
+
place-items: center;
|
|
44
|
+
width: 32px;
|
|
45
|
+
height: 32px;
|
|
46
|
+
padding: 0;
|
|
47
|
+
font-size: 1.5rem;
|
|
48
|
+
line-height: 1;
|
|
49
|
+
color: var(--rp-color-text-muted);
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
background: none;
|
|
52
|
+
border: none;
|
|
53
|
+
border-radius: var(--rp-radius-sm);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.close:hover {
|
|
57
|
+
background: var(--rp-color-surface-sunken);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.close:focus-visible {
|
|
61
|
+
outline: var(--rp-focus-ring);
|
|
62
|
+
outline-offset: var(--rp-focus-offset);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.body {
|
|
66
|
+
flex: 1;
|
|
67
|
+
padding: var(--rp-space-4);
|
|
68
|
+
overflow-y: auto;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.foot {
|
|
72
|
+
display: flex;
|
|
73
|
+
gap: var(--rp-space-2);
|
|
74
|
+
justify-content: flex-end;
|
|
75
|
+
padding: var(--rp-space-4);
|
|
76
|
+
border-top: 1px solid var(--rp-color-border);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.foot:not(:has(*)) {
|
|
80
|
+
display: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
84
|
+
.dialog {
|
|
85
|
+
animation: rp-modal-in 120ms ease-out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@keyframes rp-modal-in {
|
|
89
|
+
from {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
transform: translateY(8px);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|