diff --git a/apps/svelte.dev/src/lib/icons/download-dark.svg b/apps/svelte.dev/src/lib/icons/download-dark.svg new file mode 100644 index 0000000000..b769ca5914 --- /dev/null +++ b/apps/svelte.dev/src/lib/icons/download-dark.svg @@ -0,0 +1 @@ + diff --git a/apps/svelte.dev/src/lib/icons/download-light.svg b/apps/svelte.dev/src/lib/icons/download-light.svg new file mode 100644 index 0000000000..a1f28e2b87 --- /dev/null +++ b/apps/svelte.dev/src/lib/icons/download-light.svg @@ -0,0 +1 @@ + diff --git a/apps/svelte.dev/src/lib/icons/user-dark.svg b/apps/svelte.dev/src/lib/icons/user-dark.svg new file mode 100644 index 0000000000..91402ad157 --- /dev/null +++ b/apps/svelte.dev/src/lib/icons/user-dark.svg @@ -0,0 +1 @@ + diff --git a/apps/svelte.dev/src/lib/icons/user-light.svg b/apps/svelte.dev/src/lib/icons/user-light.svg new file mode 100644 index 0000000000..05e1cd22cf --- /dev/null +++ b/apps/svelte.dev/src/lib/icons/user-light.svg @@ -0,0 +1 @@ + diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte index 2028702f46..f976ff6677 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/+page.svelte @@ -14,8 +14,7 @@ let repl = $state() as Repl; let name = $state(data.gist.name); - let zen_mode = $state(false); - let modified_count = $state(0); + let modified = $state(false); let version = data.version; let setting_hash: any = null; @@ -51,7 +50,7 @@ if (!hash) { repl?.set({ - files: data.gist.components + files: structuredClone(data.gist.components) }); return; @@ -91,12 +90,12 @@ } function handle_change({ files }: { files: File[] }) { - const old_count = modified_count; - modified_count = files.filter((c) => c.modified).length; + const was_modified = modified; + modified = files.some((c) => c.modified); if ( - old_count === 0 && - modified_count > 0 && + !was_modified && + modified && name === data.gist.name && data.examples.some((section) => section.examples.some((example) => example.slug === data.gist.id) @@ -130,7 +129,7 @@ }} /> -
+
{#if browser} @@ -158,7 +156,7 @@ remove={handle_change} blur={() => { // Only change hash on editor blur to not pollute everyone's browser history - if (modified_count !== 0) { + if (modified) { const json = JSON.stringify({ files: repl.toJSON().files }); change_hash(json); } diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/AppControls.svelte b/apps/svelte.dev/src/routes/(authed)/playground/[id]/AppControls.svelte index 376717dbcf..dd3989b660 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/AppControls.svelte +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/AppControls.svelte @@ -10,6 +10,7 @@ import { get_app_context } from '../../app-context'; import type { Gist, User } from '$lib/db/types'; import type { File } from '@sveltejs/repl'; + import { browser } from '$app/environment'; interface Props { examples: Array<{ title: string; examples: any[] }>; @@ -17,16 +18,14 @@ repl: Repl; gist: Gist; name: string; - zen_mode: boolean; - modified_count: number; + modified: boolean; forked: (value: { gist: Gist }) => void; saved: () => void; } let { name = $bindable(), - zen_mode = $bindable(), - modified_count = $bindable(), + modified = $bindable(), user, repl, gist, @@ -41,6 +40,7 @@ let downloading = $state(false); let justSaved = $state(false); let justForked = $state(false); + let select: HTMLSelectElement; function wait(ms: number) { return new Promise((f) => setTimeout(f, ms)); @@ -84,7 +84,7 @@ const gist = await r.json(); forked({ gist }); - modified_count = 0; + modified = false; repl.markSaved(); if (intentWasSave) { @@ -146,7 +146,7 @@ throw new Error(`Received an HTTP ${r.status} response: ${error}`); } - modified_count = 0; + modified = false; repl.markSaved(); saved(); justSaved = true; @@ -209,6 +209,14 @@ export default app;` downloading = false; } + + // modifying an app should reset the ` { - goto(`/playground/${(e.target as HTMLSelectElement).value}`); + onchange={(e) => { + goto(`/playground/${e.currentTarget.value}`); }} > @@ -237,24 +246,18 @@ export default app;` (modified = true)} onfocus={(e) => e.currentTarget.select()} use:enter={(e) => (e.currentTarget as HTMLInputElement).blur()} />
- - - - - - + + {#if user} {:else} - {/if}
@@ -319,11 +331,15 @@ export default app;` .examples-select { position: relative; - } - .examples-select:has(select:focus-visible) .raised.icon { - outline: 2px solid hsla(var(--sk-theme-1-hsl), 0.6); - border-radius: var(--sk-border-radius); + &:has(select:focus-visible) .raised.icon { + outline: 2px solid hsla(var(--sk-theme-1-hsl), 0.6); + border-radius: var(--sk-border-radius); + } + + span { + pointer-events: none; + } } select { @@ -343,6 +359,7 @@ export default app;` justify-content: center; width: 3.2rem; height: 3.2rem; + user-select: none; } .icon { @@ -351,18 +368,62 @@ export default app;` font-size: var(--sk-font-size-ui-small); color: var(--sk-text-3); line-height: 1; + background: 50% 50% no-repeat; + background-size: 1.8rem; + z-index: 999; + + &[aria-label]:hover::before { + content: ''; + width: 1rem; + height: 1rem; + position: absolute; + background: var(--sk-text-3); + top: calc(100% + 0.5rem); + rotate: 45deg; + } + + &[aria-label]:hover::after { + content: attr(aria-label); + position: absolute; + top: calc(100% + 1rem); + background: var(--sk-text-3); + color: var(--sk-back-4); + padding: 0.5em 0.5em; + border-radius: var(--sk-border-radius); + } + + &.login { + width: auto; + background-image: url($lib/icons/user-light.svg); + background-position: 0.4rem 50%; + padding: 0 0.4rem 0 2.8rem; + + :root.dark & { + background-image: url($lib/icons/user-dark.svg); + } + } + + &.download { + background-image: url($lib/icons/download-light.svg); + + :root.dark & { + background-image: url($lib/icons/download-dark.svg); + } + } } .icon:hover, .icon:focus-visible { opacity: 1; } + + /* TODO use lucide-svelte, so we don't need all this customisation? */ .icon:disabled { - opacity: 0.3; - } + color: #ccc; - .icon[title^='fullscreen'] { - display: none; + :root.dark & { + color: #555; + } } input { @@ -378,30 +439,13 @@ export default app;` font-size: var(--sk-font-size-ui-medium); } - button span { - display: none; - } - .badge { - background: #ff3e00; - border-radius: 100%; - font-size: 10px; - padding: 0; - width: 15px; - height: 15px; - line-height: 15px; position: absolute; - top: 10px; - right: 0px; - } - - @media (min-width: 600px) { - .icon[title^='fullscreen'] { - display: inline; - } - - button span { - display: inline-block; - } + background: var(--sk-theme-1); + border-radius: 50%; + width: 1rem; + height: 1rem; + top: -0.2rem; + right: -0.2rem; } diff --git a/apps/svelte.dev/src/routes/(authed)/playground/[id]/UserMenu.svelte b/apps/svelte.dev/src/routes/(authed)/playground/[id]/UserMenu.svelte index 87b10cc8ff..98e53d1dbc 100644 --- a/apps/svelte.dev/src/routes/(authed)/playground/[id]/UserMenu.svelte +++ b/apps/svelte.dev/src/routes/(authed)/playground/[id]/UserMenu.svelte @@ -1,135 +1,61 @@ - -
(showMenu = false)} - use:click_outside={() => (showMenu = false)} -> - + {name} avatar + +
- {#if showMenu} - - {/if} -
+ {#snippet dropdown()} + Your saved apps + + {/snippet} + diff --git a/packages/site-kit/src/lib/components/Dropdown.svelte b/packages/site-kit/src/lib/components/Dropdown.svelte new file mode 100644 index 0000000000..d0f7674bb0 --- /dev/null +++ b/packages/site-kit/src/lib/components/Dropdown.svelte @@ -0,0 +1,78 @@ + + + + + diff --git a/packages/site-kit/src/lib/components/LinksDropdown.svelte b/packages/site-kit/src/lib/components/LinksDropdown.svelte index 9d1e5bc3a5..0fe4290a52 100644 --- a/packages/site-kit/src/lib/components/LinksDropdown.svelte +++ b/packages/site-kit/src/lib/components/LinksDropdown.svelte @@ -1,11 +1,12 @@ - - - + {/snippet} + diff --git a/packages/site-kit/src/lib/components/index.ts b/packages/site-kit/src/lib/components/index.ts index a02bd7901a..c1476449f3 100644 --- a/packages/site-kit/src/lib/components/index.ts +++ b/packages/site-kit/src/lib/components/index.ts @@ -1,4 +1,5 @@ export { default as Banners, defineBanner, fetchBanner } from './Banners.svelte'; +export { default as Dropdown } from './Dropdown.svelte'; export { default as Icon } from './Icon.svelte'; export { default as Icons } from './Icons.svelte'; export { default as Section } from './Section.svelte'; diff --git a/packages/site-kit/src/lib/nav/Nav.svelte b/packages/site-kit/src/lib/nav/Nav.svelte index a001b5e437..4135fd12e4 100644 --- a/packages/site-kit/src/lib/nav/Nav.svelte +++ b/packages/site-kit/src/lib/nav/Nav.svelte @@ -176,30 +176,36 @@ Top navigation bar for the application. It provides a slot for the left side, th position: relative; display: flex; width: 100%; - } - .menu :global(a) { - color: var(--sk-text-2); - line-height: 1; - font-size: var(--sk-font-size-ui-medium); - padding: 0.1rem 0.5rem 0 0.5rem; - white-space: nowrap; - height: 100%; - display: flex; - align-items: center; - text-decoration: none; - outline-offset: -2px; + :global { + a { + color: var(--sk-text-2); + line-height: 1; + font-size: var(--sk-font-size-ui-medium); + + white-space: nowrap; + height: 100%; + display: flex; + align-items: center; + text-decoration: none; + outline-offset: -2px; + + &:hover { + box-shadow: inset 0 -1px 0 0 var(--sk-back-5); + } + + &[aria-current='page'] { + color: var(--sk-theme-1); + box-shadow: inset 0 -1px 0 0 currentColor; + } + } - &:hover { - box-shadow: inset 0 -1px 0 0 var(--sk-back-5); + & > a { + padding: 0.1rem 0.5rem 0 0.5rem; + } } } - .menu :global(a[aria-current='page']) { - color: var(--sk-theme-1); - box-shadow: inset 0 -1px 0 0 currentColor; - } - .home-link { --padding-right: 1rem; width: 16rem; diff --git a/packages/site-kit/src/lib/styles/base.css b/packages/site-kit/src/lib/styles/base.css index d66c8f0eda..2ed0220d21 100644 --- a/packages/site-kit/src/lib/styles/base.css +++ b/packages/site-kit/src/lib/styles/base.css @@ -152,8 +152,7 @@ button { } button[disabled] { - opacity: 0.55; - pointer-events: none; + color: var(--sk-text-4); } .raised { @@ -172,7 +171,7 @@ button[disabled] { } &:disabled { - border-color: transparent; + border-color: var(--sk-back-5); border-width: 1px; } } diff --git a/packages/site-kit/src/lib/styles/tokens.css b/packages/site-kit/src/lib/styles/tokens.css index 8abaf52436..1753501a96 100644 --- a/packages/site-kit/src/lib/styles/tokens.css +++ b/packages/site-kit/src/lib/styles/tokens.css @@ -45,16 +45,16 @@ --sk-theme-2: hsl(var(--sk-theme-2-hsl)); --sk-back-1: hsl(0, 0%, 100%); - --sk-back-2: hsla(0, 0%, 100%, 1); - --sk-back-3: hsla(0, 0%, 99%, 1); + --sk-back-2: hsl(0, 0%, 100%); + --sk-back-3: hsl(0, 0%, 99%); --sk-back-4: hsl(0, 0%, 95%); --sk-back-5: hsl(0, 0%, 92%); --sk-back-6: hsl(0, 0%, 86%); - --sk-text-1: hsla(0, 0%, 0%, 0.95); - --sk-text-2: hsla(0, 0%, 0%, 0.88); - --sk-text-3: hsla(0, 0%, 0%, 0.73); - --sk-text-4: hsla(0, 0%, 0%, 0.55); + --sk-text-1: hsl(0 0 5); + --sk-text-2: hsl(0 0 12); + --sk-text-3: hsl(0 0 27); + --sk-text-4: hsl(0 0 45); --sk-scrollbar: rgba(0, 0, 0, 0.3); --sk-shadow: 0px 0px 14px rgba(0, 0, 0, 0.1);