From d475c7848d40c0bab25ab884a6337b8844539368 Mon Sep 17 00:00:00 2001 From: Judah Himango Date: Fri, 26 Jun 2026 11:40:14 -0700 Subject: [PATCH 01/13] Migrates PWABuilder from Shoelace to WebAwesome --- .github/copilot-instructions.md | 18 +- .gitignore | 1 + .../finding-your-windows-publisher-info.md | 2 +- .../image-recommendations-for-windows.md | 2 +- .../src/docs/windows/next-steps/next-steps.md | 3 +- .../publish-new-windows-app.md | 2 +- .../update-an-existing-app.md | 2 +- .../windows-store-documentation.md | 2 +- .../src/components/package-windows.ts | 271 +++--- .../Services/ClassicWindowsPackageCreator.cs | 12 +- .../Services/WindowsAppPackageUpdater.cs | 2 +- apps/pwabuilder/Frontend/.gitignore | 4 + apps/pwabuilder/Frontend/index.html | 2 +- apps/pwabuilder/Frontend/package-lock.json | 377 ++++++-- apps/pwabuilder/Frontend/package.json | 9 +- .../Frontend/public/shoelace-light-2.20.1.css | 484 ----------- .../Frontend/scripts/copy-icons.mjs | 19 + apps/pwabuilder/Frontend/src/app-index.ts | 1 + .../script/components/android-form.styles.ts | 77 ++ .../src/script/components/android-form.ts | 126 +-- .../script/components/app-button.styles.ts | 64 ++ .../src/script/components/app-button.ts | 71 +- .../components/app-file-input.styles.ts | 15 + .../src/script/components/app-file-input.ts | 26 +- .../script/components/app-footer.styles.ts | 154 ++++ .../src/script/components/app-footer.ts | 166 +--- .../script/components/app-header.styles.ts | 224 +++++ .../src/script/components/app-header.ts | 269 +----- .../app-package-form-base.styles.ts | 186 ++++ .../components/app-package-form-base.ts | 292 +++---- .../script/components/arrow-link.styles.ts | 53 ++ .../src/script/components/arrow-link.ts | 59 +- .../script/components/code-editor.styles.ts | 25 + .../src/script/components/code-editor.ts | 36 +- .../components/community-card.styles.ts | 129 +++ .../src/script/components/community-card.ts | 135 +-- .../script/components/community-hub.styles.ts | 143 ++++ .../src/script/components/community-hub.ts | 149 +--- .../components/companies-packaged.styles.ts | 164 ++++ .../script/components/companies-packaged.ts | 174 +--- .../script/components/cookie-banner.styles.ts | 84 ++ .../src/script/components/cookie-banner.ts | 89 +- .../script/components/discord-box.styles.ts | 54 ++ .../src/script/components/discord-box.ts | 62 +- .../script/components/hoist-tooltip.styles.ts | 47 + .../src/script/components/hoist-tooltip.ts | 63 +- .../script/components/hover-tooltip.styles.ts | 48 ++ .../src/script/components/hover-tooltip.ts | 53 +- .../src/script/components/info-card.styles.ts | 155 ++++ .../src/script/components/info-card.ts | 162 +--- .../components/info-circle-tooltip.styles.ts | 30 + .../script/components/info-circle-tooltip.ts | 36 +- .../src/script/components/ios-form.styles.ts | 68 ++ .../src/script/components/ios-form.ts | 305 +++---- .../src/script/components/lazy-load.ts | 11 +- .../manifest-editor-frame.styles.ts | 205 +++++ .../components/manifest-editor-frame.ts | 223 +---- .../manifest-code-form.styles.ts | 26 + .../manifest-editor/manifest-code-form.ts | 34 +- .../manifest-editor/manifest-field-tooltip.ts | 9 +- .../manifest-icons-form.styles.ts | 209 +++++ .../manifest-editor/manifest-icons-form.ts | 237 +---- .../manifest-info-form.styles.ts | 94 +- .../manifest-editor/manifest-info-form.ts | 30 +- .../manifest-platform-form.styles.ts | 297 +++++++ .../manifest-editor/manifest-platform-form.ts | 427 ++-------- .../manifest-preview-form.styles.ts | 9 + .../manifest-editor/manifest-preview-form.ts | 15 +- .../manifest-screenshots-form.styles.ts | 177 ++++ .../manifest-screenshots-form.ts | 189 +--- .../manifest-settings-form.styles.ts | 276 ++++++ .../manifest-editor/manifest-settings-form.ts | 343 +------- .../manifest-share-form.styles.ts | 329 +++++++ .../manifest-editor/manifest-share-form.ts | 406 +-------- .../pwa-manifest-editor.styles.ts | 51 ++ .../manifest-editor/pwa-manifest-editor.ts | 119 +-- .../search-extensions.styles.ts | 197 +++++ .../manifest-editor/search-extensions.ts | 230 +---- .../manifest-editor/toast.styles.ts | 33 + .../components/manifest-editor/toast.ts | 37 +- .../components/manifest-info-card.styles.ts | 149 ++++ .../script/components/manifest-info-card.ts | 200 +---- .../script/components/publish-pane.styles.ts | 601 +++++++++++++ .../src/script/components/publish-pane.ts | 694 ++------------- .../script/components/resource-hub.styles.ts | 96 +++ .../src/script/components/resource-hub.ts | 103 +-- .../script/components/share-card.styles.ts | 149 ++++ .../src/script/components/share-card.ts | 160 +--- .../script/components/success-card.styles.ts | 120 +++ .../src/script/components/success-card.ts | 127 +-- .../components/success-stories.styles.ts | 144 ++++ .../src/script/components/success-stories.ts | 151 +--- .../script/components/summit-banner.styles.ts | 115 +++ .../src/script/components/summit-banner.ts | 120 +-- .../script/components/sw-info-card.styles.ts | 123 +++ .../src/script/components/sw-info-card.ts | 166 +--- .../src/script/components/sw-panel.styles.ts | 29 + .../src/script/components/sw-panel.ts | 33 +- .../script/components/sw-selector.styles.ts | 184 ++++ .../src/script/components/sw-selector.ts | 202 +---- .../components/test-publish-pane.styles.ts | 302 +++++++ .../script/components/test-publish-pane.ts | 315 +------ .../components/todo-list-item.styles.ts | 16 +- .../src/script/components/todo-list-item.ts | 14 +- .../script/components/windows-form.styles.ts | 385 +++++++++ .../src/script/components/windows-form.ts | 806 +++++------------- .../src/script/models/manifest-fields.ts | 9 + .../Frontend/src/script/models/manifest.ts | 87 ++ .../script/models/single-field-validation.ts | 8 + .../Frontend/src/script/models/validation.ts | 18 + .../src/script/pages/app-home.styles.ts | 34 +- .../Frontend/src/script/pages/app-home.ts | 12 +- .../src/script/pages/app-report.api.ts | 2 +- .../src/script/pages/app-report.styles.ts | 75 +- .../Frontend/src/script/pages/app-report.ts | 266 +++--- .../google-play-packaging-status.styles.ts | 20 +- .../pages/google-play-packaging-status.ts | 29 +- .../script/pages/image-generator.styles.ts | 103 +++ .../src/script/pages/image-generator.ts | 115 +-- .../pages/powerplatform-publish.styles.ts | 78 ++ .../src/script/pages/powerplatform-publish.ts | 98 +-- .../Frontend/src/script/services/app-info.ts | 2 +- .../Frontend/src/script/services/manifest.ts | 2 +- .../services/publish/windows-publish.ts | 12 +- .../Frontend/src/script/services/toasts.ts | 119 ++- .../Frontend/src/script/utils/interfaces.ts | 2 +- .../src/script/utils/ios-validation.ts | 2 +- .../src/script/utils/oculus-validation.ts | 2 +- .../Frontend/src/script/utils/screenshots.ts | 2 +- .../src/script/utils/share-card-helper.ts | 6 +- .../src/script/utils/win-validation.ts | 2 +- apps/pwabuilder/Frontend/src/shoelace.ts | 4 - apps/pwabuilder/Frontend/src/webawesome.ts | 21 + apps/pwabuilder/Frontend/vite.config.ts | 8 + apps/pwabuilder/Views/Shared/_Layout.cshtml | 3 +- apps/pwabuilder/readme.md | 2 +- .../wwwroot/shoelace-light-2.20.1.css | 484 ----------- docs/builder/faq.md | 6 +- 138 files changed, 8142 insertions(+), 8414 deletions(-) create mode 100644 apps/pwabuilder/Frontend/.gitignore delete mode 100644 apps/pwabuilder/Frontend/public/shoelace-light-2.20.1.css create mode 100644 apps/pwabuilder/Frontend/scripts/copy-icons.mjs create mode 100644 apps/pwabuilder/Frontend/src/script/components/android-form.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/app-button.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/app-file-input.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/app-footer.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/app-header.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/app-package-form-base.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/arrow-link.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/code-editor.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/community-card.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/community-hub.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/companies-packaged.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/cookie-banner.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/discord-box.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/hoist-tooltip.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/hover-tooltip.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/info-card.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/info-circle-tooltip.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/ios-form.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor-frame.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor/manifest-code-form.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor/manifest-icons-form.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor/manifest-platform-form.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor/manifest-preview-form.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor/manifest-screenshots-form.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor/manifest-settings-form.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor/manifest-share-form.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor/pwa-manifest-editor.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor/search-extensions.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-editor/toast.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/manifest-info-card.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/publish-pane.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/resource-hub.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/share-card.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/success-card.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/success-stories.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/summit-banner.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/sw-info-card.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/sw-panel.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/sw-selector.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/test-publish-pane.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/components/windows-form.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/models/manifest.ts create mode 100644 apps/pwabuilder/Frontend/src/script/models/single-field-validation.ts create mode 100644 apps/pwabuilder/Frontend/src/script/models/validation.ts create mode 100644 apps/pwabuilder/Frontend/src/script/pages/image-generator.styles.ts create mode 100644 apps/pwabuilder/Frontend/src/script/pages/powerplatform-publish.styles.ts delete mode 100644 apps/pwabuilder/Frontend/src/shoelace.ts create mode 100644 apps/pwabuilder/Frontend/src/webawesome.ts delete mode 100644 apps/pwabuilder/wwwroot/shoelace-light-2.20.1.css diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a08fe58ea..ea9a32a30 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,28 +1,31 @@ --- -description: 'Guidelines for building C# and TypeScript applications' -applyTo: '**/*.cs, **/*.ts, **/*.js, **/*.css' +description: "Guidelines for building C# and TypeScript applications" +applyTo: "**/*.cs, **/*.ts, **/*.js, **/*.css" --- # Project Context + - This repository contains PWABuilder, a website for software developers that helps web devs create progressive web apps (PWAs) and publish them to app stores. -- The main repository is for PWABuilder.com, the web app. Its code is located in `apps/pwabuilder`. It is a C# web app using ASP.NET Core. It serves a SPA frontend, located in `apps/pwabuilder/Frontend`, which is built with TypeScript, Lit, and Shoelace. The frontend uses the Vite build system. It communicates with the backend via a REST API. +- The main repository is for PWABuilder.com, the web app. Its code is located in `apps/pwabuilder`. It is a C# web app using ASP.NET Core. It serves a SPA frontend, located in `apps/pwabuilder/Frontend`, which is built with TypeScript, Lit, and WebAwesome. The frontend uses the Vite build system. It communicates with the backend via a REST API. - The repository also contains some PWABuilder-related tooling. For example, `/apps/cli` contains the PWABuilder command line app, `/apps/pwabuilder-vscode` contains VSCode tooling for PWABuilder, `/docs` contains the documentation web site (docs.pwabuilder.com) for PWABuilder. - Frontend is built with Typescript for type safety - Frontend uses Lit web components for custom elements and SPA pages. -- Frontend uses the extrnal Shoelace library for prebuilt, styled components. +- Frontend uses the extrnal [WebAwesome library](https://webawesome.com/docs) for prebuilt, styled components. ## Typescript Development + - Enable strict mode in tsconfig.json for maximum type safety - Define types for method returns - Implement generic components and composables where applicable - New web components should inherit from LitElement -- Where possible, prefer to use existing web components from the Shoelace library +- Where possible, prefer to use existing web components from the [WebAwesome library](https://webawesome.com/docs/components) - Adhere to the single responsibility principle for components - For TypeScript files, use kebab-case for file names. - For custom web components built with Lit, keep them small and focused on one concern. - All TypeScript classes should be PascalCase. ## C# Development + - When writing C#, always use the latest version C#, currently C# 13 features. - Write clear and concise comments for each function. - Use file-scoped namespaces and single-line using directives. @@ -42,10 +45,11 @@ applyTo: '**/*.cs, **/*.ts, **/*.js, **/*.css' - Do not use LINQ query syntax (e.g. `from x in y select x`). Always prefer LINQ method syntax (`.Select()`, `.Where()`, ...) for consistency and clarity. - Do not insert more than one consecutive empty line, keep whitespace intentional and minimal. - Avoid excessive comments. Only add comments when the logic is non-obvious or complex — clean code should explain itself. -- All constructors, methods, events and properties must include XML documentation to support maintainability and developer tooling. +- All constructors, methods, events and properties must include XML documentation to support maintainability and developer tooling. - When the XML documentation is already defined in an interface or base class, use only `/// ` to inherit the documentation instead of duplicating it. ## General Instructions + - Make only high confidence suggestions when reviewing code changes. - Write code with good maintainability practices, including comments on why certain design decisions were made. - Handle edge cases and write clear exception handling. @@ -70,4 +74,4 @@ applyTo: '**/*.cs, **/*.ts, **/*.js, **/*.css' - Explain integration testing approaches for API endpoints. - Demonstrate how to mock dependencies for effective testing. - Show how to test authentication and authorization logic. -- Explain test-driven development principles as applied to API development. \ No newline at end of file +- Explain test-driven development principles as applied to API development. diff --git a/.gitignore b/.gitignore index 38eea0573..1a55ba0df 100644 --- a/.gitignore +++ b/.gitignore @@ -388,3 +388,4 @@ test-results/ # Ingore service worker in wwwroot, as it's generated by the build. The real service worker is in /apps/pwabuilder/frontend/public/service-worker.js. apps/pwabuilder/wwwroot/service-worker.js +.spiderloop/ diff --git a/apps/blog/src/docs/windows/finding-publisher-info/finding-your-windows-publisher-info.md b/apps/blog/src/docs/windows/finding-publisher-info/finding-your-windows-publisher-info.md index da96e50e8..4529c3b9e 100644 --- a/apps/blog/src/docs/windows/finding-publisher-info/finding-your-windows-publisher-info.md +++ b/apps/blog/src/docs/windows/finding-publisher-info/finding-your-windows-publisher-info.md @@ -24,7 +24,7 @@ In order to publish your PWA in the Microsoft Store, you'll need 3 things: 2. `Publisher ID` 3. `Publisher display name` -To get this information, go to [Windows Partner Center](https://partner.microsoft.com/dashboard) and click on your app. (Don't have an app yet? [Create one](/docs/publish-a-new-app-to-the-microsoft-store/).) +To get this information, go to [Microsoft Partner Center](https://partner.microsoft.com/dashboard) and click on your app. (Don't have an app yet? [Create one](/docs/publish-a-new-app-to-the-microsoft-store/).) Choose `Product Management` -> `Product Identity`: diff --git a/apps/blog/src/docs/windows/image-recommendations/image-recommendations-for-windows.md b/apps/blog/src/docs/windows/image-recommendations/image-recommendations-for-windows.md index 520a147b4..5fe12b0ba 100644 --- a/apps/blog/src/docs/windows/image-recommendations/image-recommendations-for-windows.md +++ b/apps/blog/src/docs/windows/image-recommendations/image-recommendations-for-windows.md @@ -238,7 +238,7 @@ Shown in the start menu when the user sets your app's tile to large size. ### Store logo The store logo icon -Shown in app installer, Windows Partner Center, the "Report an app" option in the Store, and the "Write a review" option in the Store. +Shown in app installer, Microsoft Partner Center, the "Report an app" option in the Store, and the "Write a review" option in the Store. - 50x50 - 63x63 (1.25x scale) diff --git a/apps/blog/src/docs/windows/next-steps/next-steps.md b/apps/blog/src/docs/windows/next-steps/next-steps.md index 26978a133..a2d47757b 100644 --- a/apps/blog/src/docs/windows/next-steps/next-steps.md +++ b/apps/blog/src/docs/windows/next-steps/next-steps.md @@ -46,9 +46,10 @@ Your zip file [contains 2 app packages](/docs/what-is-a-classic-package/): - `{app name}.msixbundle` - the main app package - `{app name}.classic.appxbundle` - app package that allows users on older versions of Windows (below 10.0.19041, May 2020 Update) to run your app. See our [classic app package explainer](/docs/what-is-a-classic-package/) for details. -Both packages can be submitted directly to the Microsoft Store through the [Windows Partner Center](https://partner.microsoft.com/dashboard) +Both packages can be submitted directly to the Microsoft Store through the [Microsoft Partner Center](https://partner.microsoft.com/dashboard) When you're ready to publish to the Store, you can either + - [Publish a new app in the Store](/docs/publish-a-new-app-to-the-microsoft-store/) - [Update an existing app in the Store](/docs/update-an-existing-app-in-the-microsoft-store/) diff --git a/apps/blog/src/docs/windows/publish-new-windows-app/publish-new-windows-app.md b/apps/blog/src/docs/windows/publish-new-windows-app/publish-new-windows-app.md index 2642af6ea..7db466b07 100644 --- a/apps/blog/src/docs/windows/publish-new-windows-app/publish-new-windows-app.md +++ b/apps/blog/src/docs/windows/publish-new-windows-app/publish-new-windows-app.md @@ -22,7 +22,7 @@ This guide shows how to publish your PWA as a new app in the Store. If you alrea ## Enroll in Windows developer program -Login to [Windows Partner Center](https://partner.microsoft.com/dashboard) with your Microsoft account. +Login to [Microsoft Partner Center](https://partner.microsoft.com/dashboard) with your Microsoft account. Then, go to your [dashboard](https://partner.microsoft.com/en-us/dashboard/home) to see your enrolled programs. If `Windows & Xbox` is listed, great, you're already enrolled. If not, choose `Add program`:
A screenshot that shows where the add program button is diff --git a/apps/blog/src/docs/windows/update-existing-app/update-an-existing-app.md b/apps/blog/src/docs/windows/update-existing-app/update-an-existing-app.md index 260b23e0c..a8e36901c 100644 --- a/apps/blog/src/docs/windows/update-existing-app/update-an-existing-app.md +++ b/apps/blog/src/docs/windows/update-existing-app/update-an-existing-app.md @@ -22,7 +22,7 @@ This guide shows how to publish your PWA as an update to an existing app in the ## Choose the app you want to update -Login to [Windows Partner Center](https://partner.microsoft.com/dashboard), then go to `Windows & Xbox` -> `Overview`. Your existing apps will be listed: +Login to [Microsoft Partner Center](https://partner.microsoft.com/dashboard), then go to `Windows & Xbox` -> `Overview`. Your existing apps will be listed: A screenshot that shows your existing apps list diff --git a/apps/blog/src/docs/windows/windows-store-documentation/windows-store-documentation.md b/apps/blog/src/docs/windows/windows-store-documentation/windows-store-documentation.md index 41e735d6f..12beccac7 100644 --- a/apps/blog/src/docs/windows/windows-store-documentation/windows-store-documentation.md +++ b/apps/blog/src/docs/windows/windows-store-documentation/windows-store-documentation.md @@ -26,7 +26,7 @@ Both the Microsoft Store and Windows itself natively supports PWAs as first-clas Follow these steps to get your PWA listed in the Microsoft Store: -1. Reserve an app name in the [Windows Partner Center](https://partner.microsoft.com/dashboard) as explained in this documentation [Publish your Progressive Web App to the Microsoft Store](https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/microsoft-store) +1. Reserve an app name in the [Microsoft Partner Center](https://partner.microsoft.com/dashboard) as explained in this documentation [Publish your Progressive Web App to the Microsoft Store](https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/microsoft-store) 2. Create your Microsoft Store app package on [PWA Builder](https://www.pwabuilder.com) 3. Follow [the Next steps for getting your PWA into the Microsoft Store](/docs/next-steps-for-getting-your-pwa-into-the-microsoft-store/) diff --git a/apps/pwabuilder-chromium-extension/src/components/package-windows.ts b/apps/pwabuilder-chromium-extension/src/components/package-windows.ts index c6a0dcf12..06996af48 100644 --- a/apps/pwabuilder-chromium-extension/src/components/package-windows.ts +++ b/apps/pwabuilder-chromium-extension/src/components/package-windows.ts @@ -10,11 +10,11 @@ import '@shoelace-style/shoelace/dist/components/tooltip/tooltip'; @customElement("package-windows") export class PackageWindows extends LitElement { - @state() currentManiUrl: string | undefined = undefined; - @state() windowsOptions: WindowsOptions | undefined = undefined; - - static styles = [ - css` + @state() currentManiUrl: string | undefined = undefined; + @state() windowsOptions: WindowsOptions | undefined = undefined; + + static styles = [ + css` :host { display: block; } @@ -45,144 +45,144 @@ export class PackageWindows extends LitElement { justify-content: space-between; } `, - ]; - - private _siteData!: SiteData; - - @property() - get siteData(): SiteData { - return this._siteData; - } - - set siteData(val: SiteData) { - const oldValue = this._siteData; - this._siteData = val; - - if (this._siteData && this._siteData.currentUrl && this._siteData.manifest.hasManifest) { - this.currentManiUrl = this._siteData.manifest.manifestUri; - const manifest = this._siteData.manifest.manifest; - - if (manifest) { - // set options we can find in manifest - this.setUpOptions( - this._siteData.currentUrl, - manifest.name || manifest.short_name || "My App", - "", - "", - "", - true, - "", - "" - ); - } - } + ]; + + private _siteData!: SiteData; - this.requestUpdate(); - } - - - private async packageForWindows(options: any): Promise { - let response: Response | undefined; - - try { - response = await fetch(windowsEndpoint, { - method: "POST", - body: JSON.stringify(options), - headers: new Headers({ "content-type": "application/json" }), - }); - } catch (err) { - console.error(err); + @property() + get siteData(): SiteData { + return this._siteData; } - return response; - } - - private setUpOptions( - url: string, - name: string, - packageId: string, - version: string, - classicVersion: string, - allowSigning: boolean = true, - publisherDisplayName: string, - publisherCommonName: string - ): WindowsOptions { - this.windowsOptions = { - url, - name, - packageId, - version, - allowSigning, - classicPackage: { - generate: true, - version: classicVersion, - }, - publisher: { - displayName: publisherDisplayName, - commonName: publisherCommonName, - }, - }; - - return this.windowsOptions; - } - - async handleSubmit(event: SubmitEvent) { - event.preventDefault(); - - if (event.target) { - let data = new FormData(event.target as HTMLFormElement); - - if (this.windowsOptions) { - for (var pair of data.entries()) { - switch (pair[0]) { - case "packageId": - this.windowsOptions.packageId = pair[1].toString(); - - break; - case "version": - this.windowsOptions.version = pair[1].toString(); - - break; - case "classicVersion": - this.windowsOptions.classicPackage.version = pair[1].toString(); - - break; - case "publisherDisplayName": - this.windowsOptions.publisher.displayName = pair[1].toString(); - - break; - case "publisherCommonName": - this.windowsOptions.publisher.commonName = pair[1].toString(); - - break; - - default: - break; - } + set siteData(val: SiteData) { + const oldValue = this._siteData; + this._siteData = val; + + if (this._siteData && this._siteData.currentUrl && this._siteData.manifest.hasManifest) { + this.currentManiUrl = this._siteData.manifest.manifestUri; + const manifest = this._siteData.manifest.manifest; + + if (manifest) { + // set options we can find in manifest + this.setUpOptions( + this._siteData.currentUrl, + manifest.name || manifest.short_name || "My App", + "", + "", + "", + true, + "", + "" + ); + } } - // we now have all options, lets try to generate a package - const response = await this.packageForWindows(this.windowsOptions); - if (response) { - const data = await response.blob(); - const url = URL.createObjectURL(data); + this.requestUpdate(); + } + + + private async packageForWindows(options: any): Promise { + let response: Response | undefined; - // var blob = new Blob(["text" + "file"], {type: "application/octet-stream"}); - // var url = URL.createObjectURL(blob); + try { + response = await fetch(windowsEndpoint, { + method: "POST", + body: JSON.stringify(options), + headers: new Headers({ "content-type": "application/json" }), + }); + } catch (err) { + console.error(err); + } + + return response; + } - // download the package - await chrome.downloads.download({ + private setUpOptions( + url: string, + name: string, + packageId: string, + version: string, + classicVersion: string, + allowSigning: boolean = true, + publisherDisplayName: string, + publisherCommonName: string + ): WindowsOptions { + this.windowsOptions = { url, - filename: `${this.windowsOptions.packageId}.zip`, - saveAs: true, - }); + name, + packageId, + version, + allowSigning, + classicPackage: { + generate: true, + version: classicVersion, + }, + publisher: { + displayName: publisherDisplayName, + commonName: publisherCommonName, + }, + }; + + return this.windowsOptions; + } + + async handleSubmit(event: SubmitEvent) { + event.preventDefault(); + + if (event.target) { + let data = new FormData(event.target as HTMLFormElement); + + if (this.windowsOptions) { + for (var pair of data.entries()) { + switch (pair[0]) { + case "packageId": + this.windowsOptions.packageId = pair[1].toString(); + + break; + case "version": + this.windowsOptions.version = pair[1].toString(); + + break; + case "classicVersion": + this.windowsOptions.classicPackage.version = pair[1].toString(); + + break; + case "publisherDisplayName": + this.windowsOptions.publisher.displayName = pair[1].toString(); + + break; + case "publisherCommonName": + this.windowsOptions.publisher.commonName = pair[1].toString(); + + break; + + default: + break; + } + } + + // we now have all options, lets try to generate a package + const response = await this.packageForWindows(this.windowsOptions); + if (response) { + const data = await response.blob(); + const url = URL.createObjectURL(data); + + // var blob = new Blob(["text" + "file"], {type: "application/octet-stream"}); + // var url = URL.createObjectURL(blob); + + // download the package + await chrome.downloads.download({ + url, + filename: `${this.windowsOptions.packageId}.zip`, + saveAs: true, + }); + } + } } - } } - } - render() { - return html` + render() { + return html`

Package for The Microsoft Store

Documentation @@ -193,7 +193,7 @@ export class PackageWindows extends LitElement {
The Package ID uniquely identifying your app in the Microsoft Store. - Get this value from Windows Partner Center. + Get this value from Microsoft Partner Center.
- -
- -
- ${this.renderFormInput({ - label: 'Enable', - tooltip: 'If enabled, your download will include the source code for your Android app.', - inputId: 'include-src-input', - type: 'checkbox', - checked: this.packageOptions.includeSourceCode === true, - inputHandler: (_, checked) => this.packageOptions.includeSourceCode = checked - })} -
-
- this.rotateNinety()} @sl-hide=${() => this.rotateZero()}> + this.rotateNinety()} @wa-hide=${() => this.rotateZero()}>
-

All Settings

- dropdown toggler + All Settings
@@ -565,6 +471,20 @@ export class AndroidForm extends AppPackageFormBase {
+
+ +
+ ${this.renderFormInput({ + label: 'Enable', + tooltip: 'If enabled, your download will include the source code for your Android app.', + inputId: 'include-src-input', + type: 'checkbox', + checked: this.packageOptions.includeSourceCode === true, + inputHandler: (_, checked) => this.packageOptions.includeSourceCode = checked + })} +
+
+
@@ -772,7 +692,7 @@ export class AndroidForm extends AppPackageFormBase {
-
+ diff --git a/apps/pwabuilder/Frontend/src/script/components/app-button.styles.ts b/apps/pwabuilder/Frontend/src/script/components/app-button.styles.ts new file mode 100644 index 000000000..39073a6ba --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/components/app-button.styles.ts @@ -0,0 +1,64 @@ +import { css } from "lit"; +import { mediumBreakPoint } from '../utils/css/breakpoints'; +import { fastButtonCss } from '../utils/css/fast-elements'; + +export const appButtonStyles = css` + :host { + border-radius: var(--button-radius); + display: block; + outline: none; + --font-size: var(--desktop-button-font-size); + --button-height: 44px; + --button-height: var(--desktop-button-height); + --button-square: var(--button-height); + --button-width: 127px; + --button-width: var(--button-width); + --button-font-color: var(--secondary-color); + --pading-vertical: 0; + --padding-horizontal: 34px; + } + + ${fastButtonCss} + [appearance='lightweight'] { + box-shadow: none; + } + fast-button.link { + --accent-foreground-active: var(--font-color); + --accent-foreground-hover: var(--font-color); + width: auto; + border-radius: unset; + box-shadow: none; + background-color: transparent; + } + fast-button.link::part(control) { + --padding-horizontal: 0; + width: auto; + } + fast-button.round, + fast-button.square { + height: var(--button-square); + width: var(--button-square); + } + fast-button.round::part(control), + fast-button.square::part(control) { + /* assumption is that the button is 14x21 */ + --padding-horizontal: 15px; + align-items: center; + line-height: 0; + } + fast-button:focus { + outline: solid; + outline-width: 2px; + } + + ${mediumBreakPoint( + css` + fast-button.navigation { + --button-width: 100px; + --button-height: 40px; + line-height: 28px; + font-size: 16px; + } + ` + )} + `; diff --git a/apps/pwabuilder/Frontend/src/script/components/app-button.ts b/apps/pwabuilder/Frontend/src/script/components/app-button.ts index 9103bd067..9c7d0f18f 100644 --- a/apps/pwabuilder/Frontend/src/script/components/app-button.ts +++ b/apps/pwabuilder/Frontend/src/script/components/app-button.ts @@ -1,10 +1,9 @@ -import { LitElement, css, html } from 'lit'; +import { LitElement, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; -import { mediumBreakPoint } from '../utils/css/breakpoints'; -import { fastButtonCss } from '../utils/css/fast-elements'; import { FastButtonAppearance } from '../utils/fast-element'; import { AppButtonElement } from '../utils/interfaces.components'; +import { appButtonStyles } from './app-button.styles'; @customElement('app-button') export class AppButton extends LitElement implements AppButtonElement { @@ -13,71 +12,7 @@ export class AppButton extends LitElement implements AppButtonElement { @property({ type: String }) appearance: FastButtonAppearance = 'neutral'; @property({ type: Boolean }) disabled = false; - static get styles() { - return [ - css` - :host { - border-radius: var(--button-radius); - display: block; - outline: none; - --font-size: var(--desktop-button-font-size); - --button-height: 44px; - --button-height: var(--desktop-button-height); - --button-square: var(--button-height); - --button-width: 127px; - --button-width: var(--button-width); - --button-font-color: var(--secondary-color); - --pading-vertical: 0; - --padding-horizontal: 34px; - } - `, - // fast css - fastButtonCss, - css` - [appearance='lightweight'] { - box-shadow: none; - } - fast-button.link { - --accent-foreground-active: var(--font-color); - --accent-foreground-hover: var(--font-color); - width: auto; - border-radius: unset; - box-shadow: none; - background-color: transparent; - } - fast-button.link::part(control) { - --padding-horizontal: 0; - width: auto; - } - fast-button.round, - fast-button.square { - height: var(--button-square); - width: var(--button-square); - } - fast-button.round::part(control), - fast-button.square::part(control) { - /* assumption is that the button is 14x21 */ - --padding-horizontal: 15px; - align-items: center; - line-height: 0; - } - fast-button:focus { - outline: solid; - outline-width: 2px; - } - `, - mediumBreakPoint( - css` - fast-button.navigation { - --button-width: 100px; - --button-height: 40px; - line-height: 28px; - font-size: 16px; - } - ` - ), - ]; - } + static styles = [appButtonStyles]; constructor() { super(); diff --git a/apps/pwabuilder/Frontend/src/script/components/app-file-input.styles.ts b/apps/pwabuilder/Frontend/src/script/components/app-file-input.styles.ts new file mode 100644 index 000000000..85421dca9 --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/components/app-file-input.styles.ts @@ -0,0 +1,15 @@ +import { css } from "lit"; +import { hidden } from '../utils/css/hidden'; +import { fastButtonCss } from '../utils/css/fast-elements'; + +export const appFileInputStyles = css` + [appearance='lightweight'] { + box-shadow: none; + } + :hover { + background-color: transparent; + } + + ${hidden} + ${fastButtonCss} + `; diff --git a/apps/pwabuilder/Frontend/src/script/components/app-file-input.ts b/apps/pwabuilder/Frontend/src/script/components/app-file-input.ts index 0a0fbe4d0..064a6c60e 100644 --- a/apps/pwabuilder/Frontend/src/script/components/app-file-input.ts +++ b/apps/pwabuilder/Frontend/src/script/components/app-file-input.ts @@ -1,13 +1,12 @@ -import { LitElement, css, html } from 'lit'; +import { LitElement, html } from 'lit'; import { customElement, property, state, query } from 'lit/decorators.js'; -import { hidden } from '../utils/css/hidden'; -import { fastButtonCss } from '../utils/css/fast-elements'; import { FileInputDetails, Lazy } from '../utils/interfaces'; import { ifDefined } from 'lit/directives/if-defined.js'; import { FileInputElement } from '../utils/interfaces.components'; -import '@shoelace-style/shoelace/dist/components/button/button.js'; +import { appFileInputStyles } from './app-file-input.styles'; +import '@awesome.me/webawesome/dist/components/button/button.js'; @customElement('app-file-input') export class FileInput extends LitElement implements FileInputElement { @@ -17,20 +16,7 @@ export class FileInput extends LitElement implements FileInputElement { @state() buttonText = 'Choose File'; - static get styles() { - return [ - css` - [appearance='lightweight'] { - box-shadow: none; - } - :hover { - background-color: transparent; - } - `, - hidden, - fastButtonCss, - ]; - } + static styles = [appFileInputStyles]; get input(): any { return this.fileInput; @@ -51,12 +37,12 @@ export class FileInput extends LitElement implements FileInputElement { render() { return html`
- ${this.buttonText} - + recordPWABuilderProcessStep(`footer.github_clicked`, AnalyticsBehavior.ProcessCheckpoint)} > - + recordPWABuilderProcessStep(`footer.twitter_clicked`, AnalyticsBehavior.ProcessCheckpoint)} > - + recordPWABuilderProcessStep(`footer.youtube_clicked`, AnalyticsBehavior.ProcessCheckpoint)} > - +
diff --git a/apps/pwabuilder/Frontend/src/script/components/app-header.styles.ts b/apps/pwabuilder/Frontend/src/script/components/app-header.styles.ts new file mode 100644 index 000000000..0687ec03d --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/components/app-header.styles.ts @@ -0,0 +1,224 @@ +import { css } from "lit"; +import { + xxxLargeBreakPoint, + xxLargeBreakPoint, + xLargeBreakPoint, + largeBreakPoint, + mediumBreakPoint, + smallBreakPoint, + xSmallBreakPoint, +} from '../utils/css/breakpoints'; + +export const appHeaderStyles = css` + :host { + --header-background: white; + --header-border: rgba(0, 0, 0, 0.25) solid 1px; + } + + header { + display: flex; + justify-content: space-between; + align-items: center; + padding-left: 16px; + padding-right: 16px; + background: var(--header-background); + color: white; + height: 71px; + + border-bottom: var(--header-border); + z-index: 1; + } + + header img { + cursor: pointer; + width: 100px; + height: auto; + } + + nav { + display: flex; + justify-content: flex-end; + align-items: center; + width: 8em; + gap: .75em; + } + + .nav_button { + all: unset; + } + + .nav_link { + color: var(--font-color); + text-decoration: none; + border-bottom: none; + font-weight: var(--font-bold); + font-size: var(--subheader-font-size); + margin: 0; + } + + .nav_link:focus { + outline: solid; + outline-width: 2px; + } + + .nav_link span { + display: inline-block; + height: 18px; + font-size: 20px; + } + + .nav_link:hover span{ + cursor: pointer; + } + + nav wa-icon { + font-size: 2em; + } + + .nav_link:visited { + color: black; + } + + .link { + display: block; + width: 100%; + text-decoration: none; + } + + .link:visited, .link:active, .link:link { + color: #777777; + } + + .hover-color:hover { + color: var(--primary-color); + } + + wa-dropdown::part(menu) { + display: flex; + flex-direction: column; + background-color: white; + gap: 5px; + color: #777777; + font-size: 16px; + border-radius: 5px; + height: fit-content; + width: 136px; + padding: 16px 22px; + } + + wa-dropdown-item::part(checkmark), wa-dropdown-item::part(submenu-icon) { + display: none; + } + + wa-dropdown-item::part(label) { + color: #777777; + text-decoration: none; + border-bottom: none; + font-size: 14px; + margin: 0; + padding: 0; + } + + wa-dropdown-item:focus-visible, wa-dropdown-item:hover { + background-color: transparent; + } + + wa-dropdown-item:focus-visible::part(label), wa-dropdown-item:hover::part(label) { + color: var(--primary-color); + background-color: transparent; + font-weight: 700; + } + + wa-dropdown-item:focus-visible .link, wa-dropdown-item:hover .link { + color: var(--primary-color); + background-color: transparent; + font-weight: 700; + } + + wa-dropdown { + position: relative; + + } + wa-dropdown::part(base){ + box-shadow: 0px 16px 24px 0px #00000026; + } + + .col-header { + text-decoration: none; + margin: 0; + white-space: nowrap; + font-weight: bold; + color: #777777; + padding: 0; + font-size: 14px; + } + + @media (prefers-color-scheme: light) { + header { + color: black; + } + } + + ${xSmallBreakPoint(css` + header { + padding-left: 8px; + padding-right: 8px; + } + + header img { + width: 60px; + } + + nav { + width: auto; + gap: 0.5em; + } + + .nav_link span { + font-size: 16px; + } + `)} + + ${smallBreakPoint(css` + + `)} + + ${mediumBreakPoint(css` + header nav { + display: initial; + } + + #desktop-nav { + display: flex; + } + + `)} + + + ${largeBreakPoint(css` + #desktop-nav { + display: flex; + } + + `)} + + ${xLargeBreakPoint(css` + header { + padding-left: 1em; + padding-right: 1em; + } + `)} + + ${xxLargeBreakPoint(css` + header { + padding-left: 3em; + padding-right: 3em; + } + `)} + + ${xxxLargeBreakPoint(css` + header { + background-color: white; + } + `)} + `; diff --git a/apps/pwabuilder/Frontend/src/script/components/app-header.ts b/apps/pwabuilder/Frontend/src/script/components/app-header.ts index 803f9d44b..10f4fe4d6 100644 --- a/apps/pwabuilder/Frontend/src/script/components/app-header.ts +++ b/apps/pwabuilder/Frontend/src/script/components/app-header.ts @@ -1,246 +1,16 @@ import { Router } from '@vaadin/router'; -import { LitElement, css, html } from 'lit'; +import { LitElement, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { AnalyticsBehavior, recordPWABuilderProcessStep } from '../utils/analytics'; -import '@shoelace-style/shoelace/dist/components/dropdown/dropdown.js'; -import '@shoelace-style/shoelace/dist/components/menu/menu.js'; -import '@shoelace-style/shoelace/dist/components/menu-item/menu-item.js'; -import { - xxxLargeBreakPoint, - xxLargeBreakPoint, - xLargeBreakPoint, - largeBreakPoint, - mediumBreakPoint, - smallBreakPoint, - xSmallBreakPoint, -} from '../utils/css/breakpoints'; +import { appHeaderStyles } from './app-header.styles'; +import '@awesome.me/webawesome/dist/components/dropdown/dropdown.js'; +import '@awesome.me/webawesome/dist/components/dropdown-item/dropdown-item.js'; @customElement('app-header') export class AppHeader extends LitElement { @property({ type: String }) page = 'home'; - static get styles() { - return css` - :host { - --header-background: white; - --header-border: rgba(0, 0, 0, 0.25) solid 1px; - } - - header { - display: flex; - justify-content: space-between; - align-items: center; - padding-left: 16px; - padding-right: 16px; - background: var(--header-background); - color: white; - height: 71px; - - border-bottom: var(--header-border); - z-index: 1; - } - - header img { - cursor: pointer; - width: 100px; - height: auto; - } - - nav { - display: flex; - justify-content: flex-end; - align-items: center; - width: 8em; - gap: .75em; - } - - .nav_button { - all: unset; - } - - .nav_link { - color: var(--font-color); - text-decoration: none; - border-bottom: none; - font-weight: var(--font-bold); - font-size: var(--subheader-font-size); - margin: 0; - } - - .nav_link:focus { - outline: solid; - outline-width: 2px; - } - - .nav_link span { - display: inline-block; - height: 18px; - font-size: 20px; - } - - .nav_link:hover span{ - cursor: pointer; - } - - nav sl-icon { - font-size: 2em; - } - - .nav_link:visited { - color: black; - } - - .link { - display: block; - width: 100%; - text-decoration: none; - } - - .link:visited, .link:active, .link:link { - color: #777777; - } - - .hover-color:hover { - color: var(--primary-color); - } - - sl-menu { - display: flex; - flex-direction: column; - background-color: white; - gap: 5px; - color: #777777; - font-size: 16px; - border-radius: 5px; - height: fit-content; - width: 136px; - padding: 16px 22px; - } - - sl-menu-item::part(checked-icon), sl-menu-item::part(submenu-icon) { - display: none; - } - - sl-menu-item::part(base){ - color: #777777; - text-decoration: none; - border-bottom: none; - font-size: 14px; - margin: 0; - padding: 0; - } - - sl-menu-item::part(base):hover sl-menu-item::part(label) { - background-color: unset; - color: var(--primary-color); - } - - sl-menu-item:focus-visible::part(base) { - color: var(--primary-color); - background-color: transparent; - - } - - sl-menu-item:hover::part(base) { - color: var(--primary-color); - background-color: transparent; - font-weight: 700; - } - - sl-menu-item:focus-visible .link, sl-menu-item:hover .link { - color: var(--primary-color); - background-color: transparent; - font-weight: 700; - } - - sl-dropdown { - position: relative; - - } - sl-dropdown::part(base){ - box-shadow: 0px 16px 24px 0px #00000026; - } - - .col-header { - text-decoration: none; - margin: 0; - white-space: nowrap; - font-weight: bold; - color: #777777; - padding: 0; - font-size: 14px; - } - - @media (prefers-color-scheme: light) { - header { - color: black; - } - } - - ${xSmallBreakPoint(css` - header { - padding-left: 8px; - padding-right: 8px; - } - - header img { - width: 60px; - } - - nav { - width: auto; - gap: 0.5em; - } - - .nav_link span { - font-size: 16px; - } - `)} - - ${smallBreakPoint(css` - - `)} - - ${mediumBreakPoint(css` - header nav { - display: initial; - } - - #desktop-nav { - display: flex; - } - - `)} - - - ${largeBreakPoint(css` - #desktop-nav { - display: flex; - } - - `)} - - ${xLargeBreakPoint(css` - header { - padding-left: 1em; - padding-right: 1em; - } - `)} - - ${xxLargeBreakPoint(css` - header { - padding-left: 3em; - padding-right: 3em; - } - `)} - - ${xxxLargeBreakPoint(css` - header { - background-color: white; - } - `)} - `; - } + static styles = [appHeaderStyles]; constructor() { super(); @@ -264,19 +34,18 @@ export class AppHeader extends LitElement { } showMenu(){ - let menu = this.shadowRoot!.querySelector("sl-dropdown"); + let menu = this.shadowRoot!.querySelector("wa-dropdown"); if(menu!.open){ recordPWABuilderProcessStep(`header.community_dropdown_closed`, AnalyticsBehavior.ProcessCheckpoint) - menu!.hide() + menu!.open = false; } else { recordPWABuilderProcessStep(`header.community_dropdown_expanded`, AnalyticsBehavior.ProcessCheckpoint) - menu!.show(); - + menu!.open = true; } } // hacky work around for clicking links with keyboard that are nested in menu items - // in the future, shoelace may make a thing but for now this works. + // in the future, Web Awesome may make a thing but for now this works. handleClickingLink(linkTag: string, analyticsString: string){ const anchor: HTMLAnchorElement = this.shadowRoot!.querySelector('[data-tag="' + linkTag + '"]')!; anchor.click(); @@ -318,12 +87,11 @@ export class AppHeader extends LitElement { ` : null } - + - - +

Follow us on

- this.handleClickingLink("github_link", "header.github_clicked")}> + this.handleClickingLink("github_link", "header.github_clicked")}> Github - - this.handleClickingLink("twitter_link", "header.twitter_clicked")}> +
+ this.handleClickingLink("twitter_link", "header.twitter_clicked")}> X -
- this.handleClickingLink("discord_link", "header.discord_clicked")}> + + this.handleClickingLink("discord_link", "header.discord_clicked")}> Discord - - - + + `; diff --git a/apps/pwabuilder/Frontend/src/script/components/app-package-form-base.styles.ts b/apps/pwabuilder/Frontend/src/script/components/app-package-form-base.styles.ts new file mode 100644 index 000000000..3251fcb7a --- /dev/null +++ b/apps/pwabuilder/Frontend/src/script/components/app-package-form-base.styles.ts @@ -0,0 +1,186 @@ +import { css } from "lit"; + +export const appPackageFormBaseStyles = css` + #form-layout input { + border: 1px solid rgba(194, 201, 209, 1); + border-radius: var(--input-border-radius); + color: var(--font-color); + } + + #form-layout input:not([type='color']) { + padding: 10px; + } + + input::placeholder { + color: var(--placeholder-color); + font-style: italic; + } + + #form-extras wa-button::part(base) { + background-color: var(--font-color); + color: #ffffff; + font-size: 14px; + height: 3em; + width: 25%; + border-radius: var(--button-border-radius); + } + + #form-extras wa-button::part(label){ + display: flex; + align-items: center; + } + + #form-layout { + overflow-y: auto; + padding: 0em 1.5em 0 1em; + } + + .tooltip { + margin-left: 10px; + } + + .form-group .tooltip a { + color: #fff; + } + + .form-group { + display: flex; + flex-direction: column; + } + + .form-group label { + font-size: var(--small-medium-font-size); + font-weight: bold; + line-height: 40px; + display: flex; + align-items: center; + } + + .form-group label a { + text-decoration: none; + color: var(--font-color); + } + + /* Inline required asterisk, matching WebAwesome's required-indicator tokens. */ + .required-indicator { + color: var(--wa-form-control-required-content-color); + margin-inline-start: var(--wa-form-control-required-content-offset); + } + + /* Suppress wa-input's built-in required asterisk: we render no label inside + wa-input, so its asterisk would otherwise appear alone on its own line. + We show the asterisk inline next to our own