| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- <script>
- import {notify} from "$lib/notifier.svelte.js";
- import {goto} from "$app/navigation";
- let nameInput, quantityInput, stepsInput;
- let name = $state("");
- let quantity = $state();
- let unit = $state("");
- let ingredientName = $state("");
- let ingredients = $state([]);
- let stepString = $state("");
- let steps = $state([]);
- let notes = $state("");
- const submit = async (event)=>{
- event.preventDefault();
- if(!name){
- notify("error", "Please enter recipe name");
- nameInput.focus();
- return;
- }
- const data = {name, ingredients, steps, notes};
- const response = await fetch("/recipe/new", {
- method: "POST",
- headers: {"Content-Type": "application/json"},
- credentials: "include",
- body: JSON.stringify(data)
- });
- const recipe = await response.json();
- if(!response.ok) return notify("error", recipe.message);
- goto("/dashboard");
- }
- const addIngredient = ()=>{
- if(quantity <= 0) return notify("error", "Quantity must be greater than 0");
- if(!unit) return notify("error", "Unit is required");
- if(!ingredientName) return notify("error", "Ingredient name is required");
- ingredients.push({
- quantity: quantity,
- unit: unit,
- name: ingredientName
- });
- quantity = null;
- unit = "";
- ingredientName = "";
- quantityInput.focus();
- }
- const addStep = ()=>{
- if(!stepString) return notify("error", "Please enter some text");
- steps.push(stepString);
- stepString = "";
- stepsInput.focus();
- }
- $effect(()=>{
- nameInput?.focus();
- });
- </script>
- <div class="container">
- <form class="standardForm" onsubmit={submit}>
- <h1>New Recipe</h1>
- <label>Name
- <input type="text" bind:this={nameInput} bind:value={name}>
- </label>
- <div class="ingredients">
- <h2>Ingredients</h2>
- <ul>
- {#each ingredients as i}
- <li>{i.name}, {i.quantity} {i.unit}</li>
- {/each}
- </ul>
- <label>Quantity
- <input type="number" min="0" bind:value={quantity} bind:this={quantityInput}>
- </label>
- <label>Unit
- <input type="text" bind:value={unit}>
- </label>
- <label>Name
- <input type="text" bind:value={ingredientName}>
- </label>
- <button class="button" type="button" onclick={addIngredient}>Add</button>
- </div>
- <div class="steps">
- <h2>Steps</h2>
- <ol>
- {#each steps as s}
- <li>{s}</li>
- {/each}
- </ol>
- <textarea bind:value={stepString} rows="2" bind:this={stepsInput}></textarea>
- <button class="button" type="button" onclick={addStep}>Add</button>
- </div>
- <textarea bind:value={notes} rows="4" placeholder="Notes"></textarea>
- <button>Create Recipe</button>
- </form>
- </div>
- <style>
- .container{
- display: flex;
- justify-content: center;
- align-items: flex-start;
- width: 100%;
- max-width: 100%;
- min-height: calc(100% - 75px);
- min-height: calc(100dvh - 75px);
- padding: 1rem max(0.75rem, env(safe-area-inset-right, 0px)) max(1.5rem, env(safe-area-inset-bottom, 0px)) max(0.75rem, env(safe-area-inset-left, 0px));
- color: var(--color-text);
- overflow-x: clip;
- box-sizing: border-box;
- }
- .standardForm{
- width: min(640px, 100%);
- max-width: 100%;
- min-width: 0;
- padding: 1.15rem 0.9rem;
- container-type: inline-size;
- container-name: recipe-form;
- }
- h1{
- font-size: clamp(1.25rem, 1rem + 2vw, 1.5rem);
- }
- h2{
- color: var(--color-text);
- font-size: clamp(1rem, 0.9rem + 0.5vw, 1.1rem);
- font-weight: 700;
- letter-spacing: 0.01em;
- }
- input,
- textarea{
- width: 100%;
- min-width: 0;
- max-width: 100%;
- font-size: 1rem;
- }
- .ingredients,
- .steps{
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
- padding: 0.75rem;
- min-width: 0;
- max-width: 100%;
- background: var(--color-surface-alt);
- border: 1px solid var(--color-border);
- border-radius: 8px;
- }
- ul,
- ol{
- margin: 0;
- padding: 0;
- display: flex;
- flex-direction: column;
- gap: 0.45rem;
- list-style: none;
- min-width: 0;
- max-width: 100%;
- }
- ul:not(:has(li)),
- ol:not(:has(li)){
- display: none;
- }
- li{
- padding: 0.55rem 0.7rem;
- background: var(--color-surface);
- border: 1px solid var(--color-border);
- border-left: 3px solid var(--color-primary);
- border-radius: 6px;
- font-size: 0.95rem;
- line-height: 1.4;
- color: var(--color-text);
- overflow-wrap: anywhere;
- word-break: break-word;
- min-width: 0;
- max-width: 100%;
- }
- ol{
- counter-reset: step;
- }
- ol li{
- display: grid;
- grid-template-columns: 1.5rem minmax(0, 1fr);
- align-items: start;
- gap: 0.7rem;
- counter-increment: step;
- }
- ol li::before{
- content: counter(step);
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 1.5rem;
- height: 1.5rem;
- border-radius: 50%;
- background: var(--color-primary);
- color: var(--color-text-on-primary);
- font-size: 0.75rem;
- font-weight: 700;
- line-height: 1;
- }
- .ingredients{
- display: grid;
- grid-template-columns: minmax(0, 1fr);
- align-items: stretch;
- gap: 0.75rem;
- }
- .ingredients > *{
- min-width: 0;
- max-width: 100%;
- }
- .button{
- margin: 0;
- width: 100%;
- height: auto;
- min-height: 2.5rem;
- white-space: nowrap;
- }
- textarea{
- resize: vertical;
- min-height: 3.5rem;
- }
- .standardForm > textarea{
- min-height: 5rem;
- }
- @container recipe-form (min-width: 260px){
- .ingredients{
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
- align-items: end;
- }
- .ingredients h2,
- .ingredients ul,
- .ingredients label:last-of-type,
- .ingredients .button{
- grid-column: 1 / -1;
- }
- }
- @container recipe-form (min-width: 480px){
- .ingredients,
- .steps{
- padding: 1rem;
- }
- .ingredients{
- grid-template-columns: minmax(0, 5.5rem) minmax(0, 6.5rem) minmax(0, 1fr) auto;
- align-items: end;
- }
- .ingredients h2,
- .ingredients ul{
- grid-column: 1 / -1;
- }
- .ingredients label:last-of-type,
- .ingredients .button{
- grid-column: auto;
- }
- .ingredients .button{
- width: auto;
- min-height: 0;
- height: fit-content;
- }
- }
- @media (min-width: 640px){
- .container{
- padding: 2rem 1rem 3rem;
- }
- .standardForm{
- padding: 2rem;
- }
- .standardForm > textarea{
- min-height: 6rem;
- }
- }
- @media (max-height: 500px){
- .container{
- padding-top: 0.75rem;
- padding-bottom: 1rem;
- }
- .standardForm > textarea{
- min-height: 3.5rem;
- }
- }
- @media (pointer: coarse){
- .button,
- .standardForm button{
- min-height: 44px;
- }
- }
- </style>
|