diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6de550a8c..569a9cfa3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -359,7 +359,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: unraid-wc-rich - path: web/.nuxt/nuxt-custom-elements/dist/unraid-components + path: web/.nuxt/standalone-apps build-plugin-staging-pr: name: Build and Deploy Plugin diff --git a/@tailwind-shared/base-utilities.css b/@tailwind-shared/base-utilities.css index 6818e30d6..89437858c 100644 --- a/@tailwind-shared/base-utilities.css +++ b/@tailwind-shared/base-utilities.css @@ -1,7 +1,7 @@ @custom-variant dark (&:where(.dark, .dark *)); -@layer utilities { - :host { +/* Utility defaults for web components (when we were using shadow DOM) */ +:host { --tw-divide-y-reverse: 0; --tw-border-style: solid; --tw-font-weight: initial; @@ -48,21 +48,20 @@ --tw-drop-shadow: initial; --tw-duration: initial; --tw-ease: initial; - } } -@layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: hsl(var(--border)); - } +/* Global border color - this is what's causing the issue! */ +/* Commenting out since it affects all elements globally +*, +::after, +::before, +::backdrop, +::file-selector-button { + border-color: hsl(var(--border)); +} +*/ - - - body { +body { --color-alpha: #1c1b1b; --color-beta: #f2f2f2; --color-gamma: #999999; @@ -74,8 +73,7 @@ --ring-shadow: 0 0 var(--color-beta); } - button:not(:disabled), - [role='button']:not(:disabled) { - cursor: pointer; - } +button:not(:disabled), +[role='button']:not(:disabled) { + cursor: pointer; } \ No newline at end of file diff --git a/@tailwind-shared/css-variables.css b/@tailwind-shared/css-variables.css index 9ddc00dcf..9861d76ff 100644 --- a/@tailwind-shared/css-variables.css +++ b/@tailwind-shared/css-variables.css @@ -1,7 +1,11 @@ /* Hybrid theme system: Native CSS + Theme Store fallback */ -@layer base { - /* Light mode defaults */ - :root { + +/* Light mode defaults */ +:root { + /* Override Tailwind v4 global styles to use webgui variables */ + --ui-bg: var(--background-color) !important; + --ui-text: var(--text-color) !important; + --background: 0 0% 100%; --foreground: 0 0% 3.9%; --muted: 0 0% 96.1%; @@ -30,6 +34,10 @@ /* Dark mode */ .dark { + /* Override Tailwind v4 global styles to use webgui variables */ + --ui-bg: var(--background-color) !important; + --ui-text: var(--text-color) !important; + --background: 0 0% 3.9%; --foreground: 0 0% 98%; --muted: 0 0% 14.9%; @@ -62,69 +70,4 @@ --background: 0 0% 3.9%; --foreground: 0 0% 98%; --border: 0 0% 14.9%; - } - - /* For web components: inherit CSS variables from the host */ - :host { - --background: inherit; - --foreground: inherit; - --muted: inherit; - --muted-foreground: inherit; - --popover: inherit; - --popover-foreground: inherit; - --card: inherit; - --card-foreground: inherit; - --border: inherit; - --input: inherit; - --primary: inherit; - --primary-foreground: inherit; - --secondary: inherit; - --secondary-foreground: inherit; - --accent: inherit; - --accent-foreground: inherit; - --destructive: inherit; - --destructive-foreground: inherit; - --ring: inherit; - --chart-1: inherit; - --chart-2: inherit; - --chart-3: inherit; - --chart-4: inherit; - --chart-5: inherit; - } - - /* Class-based dark mode support for web components using :host-context */ - :host-context(.dark) { - --background: 0 0% 3.9%; - --foreground: 0 0% 98%; - --muted: 0 0% 14.9%; - --muted-foreground: 0 0% 63.9%; - --popover: 0 0% 3.9%; - --popover-foreground: 0 0% 98%; - --card: 0 0% 3.9%; - --card-foreground: 0 0% 98%; - --border: 0 0% 14.9%; - --input: 0 0% 14.9%; - --primary: 0 0% 98%; - --primary-foreground: 0 0% 9%; - --secondary: 0 0% 14.9%; - --secondary-foreground: 0 0% 98%; - --accent: 0 0% 14.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --ring: 0 0% 83.1%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - } - - /* Alternative class-based dark mode support for specific Unraid themes */ - :host-context(.dark[data-theme='black']), - :host-context(.dark[data-theme='gray']) { - --background: 0 0% 3.9%; - --foreground: 0 0% 98%; - --border: 0 0% 14.9%; - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/@tailwind-shared/index.css b/@tailwind-shared/index.css index b26482e77..f4af8b492 100644 --- a/@tailwind-shared/index.css +++ b/@tailwind-shared/index.css @@ -2,4 +2,5 @@ @import './css-variables.css'; @import './unraid-theme.css'; @import './base-utilities.css'; -@import './sonner.css'; \ No newline at end of file +@import './sonner.css'; +@import './reka-resets.css'; \ No newline at end of file diff --git a/@tailwind-shared/reka-resets.css b/@tailwind-shared/reka-resets.css new file mode 100644 index 000000000..52d32b38c --- /dev/null +++ b/@tailwind-shared/reka-resets.css @@ -0,0 +1,21 @@ +/* + * Minimal resets for reka-ui components + * Only override the problematic webgui button styles + */ + +/* Target all reka-ui buttons by their common attributes */ +button[id^="reka-accordion-trigger"], +button[role="combobox"], +button[aria-haspopup="menu"], +[role="dialog"] button[type="button"] { + /* Only override the truly problematic styles */ + font-family: inherit !important; /* Don't force clear-sans */ + font-size: inherit !important; /* Don't force 1.1rem */ + font-weight: normal !important; /* Don't force bold */ + letter-spacing: normal !important; /* Don't force 1.8px spacing */ + text-transform: none !important; /* Don't force uppercase */ + min-width: auto !important; /* Don't force 86px minimum */ + margin: 0 !important; /* Don't add 10px margins */ + border: none !important; /* Remove forced border */ + /* Let components handle their own padding through Tailwind classes */ +} \ No newline at end of file diff --git a/plugin/builder/build-txz.ts b/plugin/builder/build-txz.ts index 524676e8a..be97ab311 100644 --- a/plugin/builder/build-txz.ts +++ b/plugin/builder/build-txz.ts @@ -29,7 +29,9 @@ const findManifestFiles = async (dir: string): Promise => { } } else if ( entry.isFile() && - (entry.name === "manifest.json" || entry.name === "ui.manifest.json") + (entry.name === "manifest.json" || + entry.name === "ui.manifest.json" || + entry.name === "standalone.manifest.json") ) { files.push(entry.name); } @@ -124,19 +126,21 @@ const validateSourceDir = async (validatedEnv: TxzEnv) => { const manifestFiles = await findManifestFiles(webcomponentDir); const hasManifest = manifestFiles.includes("manifest.json"); + const hasStandaloneManifest = manifestFiles.includes("standalone.manifest.json"); const hasUiManifest = manifestFiles.includes("ui.manifest.json"); - if (!hasManifest || !hasUiManifest) { + // Accept either manifest.json (old web components) or standalone.manifest.json (new standalone apps) + if ((!hasManifest && !hasStandaloneManifest) || !hasUiManifest) { console.log("Existing Manifest Files:", manifestFiles); const missingFiles: string[] = []; - if (!hasManifest) missingFiles.push("manifest.json"); + if (!hasManifest && !hasStandaloneManifest) missingFiles.push("manifest.json or standalone.manifest.json"); if (!hasUiManifest) missingFiles.push("ui.manifest.json"); throw new Error( `Webcomponents missing required file(s): ${missingFiles.join(", ")} - ` + `${!hasUiManifest ? "run 'pnpm build:wc' in unraid-ui for ui.manifest.json" : ""}` + - `${!hasManifest && !hasUiManifest ? " and " : ""}` + - `${!hasManifest ? "run 'pnpm build' in web for manifest.json" : ""}` + `${(!hasManifest && !hasStandaloneManifest) && !hasUiManifest ? " and " : ""}` + + `${(!hasManifest && !hasStandaloneManifest) ? "run 'pnpm build' in web for standalone.manifest.json" : ""}` ); } diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 6ebddb974..e86b0e114 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -161,7 +161,41 @@ exit 0 sed -i 's||\n|' "/usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php" fi fi + + ]]> + + + + + ]]> @@ -272,6 +306,27 @@ exit 0 [ -f "$FILE-" ] && mv -f "$FILE-" "$FILE" done + # Restore CSS files from backup + echo "Restoring original CSS files..." + CSS_DIR="/usr/local/emhttp/plugins/dynamix/styles" + BACKUP_DIR="$CSS_DIR/.unraid-api-backup" + + if [ -d "$BACKUP_DIR" ]; then + for backup_file in "$BACKUP_DIR"/*.css; do + if [ -f "$backup_file" ]; then + filename=$(basename "$backup_file") + original_file="$CSS_DIR/$filename" + echo " Restoring $filename..." + cp "$backup_file" "$original_file" + fi + done + # Remove backup directory after restoration + rm -rf "$BACKUP_DIR" + echo "CSS restoration complete." + else + echo "No CSS backup found, skipping restoration." + fi + # Handle the unraid-components directory DIR=/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components # Remove the archive's contents before restoring diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php index ae8e439e0..01ae5590e 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php @@ -8,6 +8,7 @@ class WebComponentsExtractor private const RICH_COMPONENTS_ENTRY_JS = 'unraid-components.client.js'; private const UI_ENTRY = 'src/register.ts'; private const UI_STYLES_ENTRY = 'style.css'; + private const STANDALONE_APPS_ENTRY = 'standalone-apps.js'; private static ?WebComponentsExtractor $instance = null; @@ -98,6 +99,57 @@ class WebComponentsExtractor '; } + private function getStandaloneAppsScript(): string + { + $manifestFiles = $this->findManifestFiles('standalone.manifest.json'); + + if (empty($manifestFiles)) { + // No standalone apps, return empty + return ''; + } + + // Iterate over all manifest files to find valid standalone apps entry + foreach ($manifestFiles as $manifestPath) { + $manifest = $this->getManifestContents($manifestPath); + $subfolder = $this->getRelativePath($manifestPath); + + // Check if STANDALONE_APPS_ENTRY exists + if (!isset($manifest[self::STANDALONE_APPS_ENTRY])) { + error_log("Standalone apps manifest at '{$manifestPath}' is missing the '" . self::STANDALONE_APPS_ENTRY . "' entry key"); + return ''; + } + + $entry = $manifest[self::STANDALONE_APPS_ENTRY]; + + // Check if 'file' key exists + if (!isset($entry['file']) || empty($entry['file'])) { + error_log("Standalone apps manifest at '{$manifestPath}' has entry '" . self::STANDALONE_APPS_ENTRY . "' but is missing the 'file' field"); + return ''; + } + + // Build the JS file path + $jsFile = ($subfolder ? $subfolder . '/' : '') . $entry['file']; + + // Use a unique identifier to prevent duplicate script loading + $scriptId = 'unraid-standalone-apps-script'; + return ' + '; + } + + // Return empty string if no valid standalone apps entry found + return ''; + } + private function getUnraidUiScriptHtml(): string { $manifestFiles = $this->findManifestFiles('ui.manifest.json'); @@ -173,7 +225,9 @@ class WebComponentsExtractor try { $scriptsOutput = true; - return $this->getRichComponentsScript() . $this->getUnraidUiScriptHtml(); + return $this->getRichComponentsScript() . + $this->getUnraidUiScriptHtml() . + $this->getStandaloneAppsScript(); } catch (\Exception $e) { error_log("Error in WebComponentsExtractor::getScriptTagHtml: " . $e->getMessage()); $scriptsOutput = false; // Reset on error diff --git a/unraid-ui/eslint.config.ts b/unraid-ui/eslint.config.ts index 83f4c47dd..3f2797bf7 100644 --- a/unraid-ui/eslint.config.ts +++ b/unraid-ui/eslint.config.ts @@ -87,6 +87,13 @@ const commonGlobals = { HTMLElement: 'readonly', HTMLInputElement: 'readonly', CustomEvent: 'readonly', + MouseEvent: 'readonly', + KeyboardEvent: 'readonly', + FocusEvent: 'readonly', + PointerEvent: 'readonly', + TouchEvent: 'readonly', + WheelEvent: 'readonly', + DragEvent: 'readonly', }; export default [// Base config from recommended configs diff --git a/unraid-ui/src/components.ts b/unraid-ui/src/components.ts index 182d8c1f0..5501213dc 100644 --- a/unraid-ui/src/components.ts +++ b/unraid-ui/src/components.ts @@ -17,6 +17,7 @@ export * from '@/components/common/tabs'; export * from '@/components/common/tooltip'; export * from '@/components/common/toast'; export * from '@/components/common/popover'; +export * from '@/components/common/responsive-modal'; export * from '@/components/modals'; export * from '@/components/common/accordion'; export * from '@/components/common/dialog'; diff --git a/unraid-ui/src/components/brand/BrandButton.vue b/unraid-ui/src/components/brand/BrandButton.vue index abc881889..53998657d 100644 --- a/unraid-ui/src/components/brand/BrandButton.vue +++ b/unraid-ui/src/components/brand/BrandButton.vue @@ -7,7 +7,6 @@ export interface BrandButtonProps { variant?: BrandButtonVariants['variant']; size?: BrandButtonVariants['size']; padding?: BrandButtonVariants['padding']; - btnType?: 'button' | 'submit' | 'reset'; class?: string; click?: () => void; disabled?: boolean; @@ -26,7 +25,6 @@ const props = withDefaults(defineProps(), { variant: 'fill', size: '16px', padding: 'default', - btnType: 'button', class: undefined, click: undefined, disabled: false, @@ -58,15 +56,26 @@ const needsBrandGradientBackground = computed(() => {