Jelajahi Sumber

feat: add dark theme

Ahmad Kholid 3 tahun lalu
induk
melakukan
0eb290b37f
46 mengubah file dengan 238 tambahan dan 99 penghapusan
  1. 2 3
      src/assets/css/drawflow.css
  2. 16 1
      src/assets/css/tailwind.css
  3. TEMPAT SAMPAH
      src/assets/images/tile-white.png
  4. 1 1
      src/background/workflow-engine/blocks-handler/handler-loop-data.js
  5. 4 2
      src/components/block/BlockBase.vue
  6. 3 3
      src/components/block/BlockBasic.vue
  7. 2 2
      src/components/block/BlockConditions.vue
  8. 2 2
      src/components/block/BlockElementExists.vue
  9. 1 1
      src/components/block/BlockGroup.vue
  10. 1 1
      src/components/block/BlockLoopBreakpoint.vue
  11. 5 5
      src/components/block/BlockRepeatTask.vue
  12. 2 2
      src/components/newtab/app/AppSidebar.vue
  13. 4 2
      src/components/newtab/logs/LogsFilters.vue
  14. 3 3
      src/components/newtab/settings/SettingsBackup.vue
  15. 3 1
      src/components/newtab/shared/SharedCard.vue
  16. 7 7
      src/components/newtab/shared/SharedLogsTable.vue
  17. 1 1
      src/components/newtab/shared/SharedWorkflowState.vue
  18. 2 2
      src/components/newtab/workflow/WorkflowActions.vue
  19. 8 2
      src/components/newtab/workflow/WorkflowBuilder.vue
  20. 4 2
      src/components/newtab/workflow/WorkflowDetailsCard.vue
  21. 1 1
      src/components/newtab/workflow/WorkflowEditBlock.vue
  22. 1 1
      src/components/newtab/workflow/WorkflowRunning.vue
  23. 3 3
      src/components/newtab/workflow/edit/EditConditions.vue
  24. 1 1
      src/components/newtab/workflow/edit/EditNewWindow.vue
  25. 1 1
      src/components/newtab/workflow/edit/EditTakeScreenshot.vue
  26. 2 2
      src/components/ui/UiButton.vue
  27. 2 2
      src/components/ui/UiCheckbox.vue
  28. 1 1
      src/components/ui/UiModal.vue
  29. 1 1
      src/components/ui/UiPopover.vue
  30. 1 0
      src/components/ui/UiSwitch.vue
  31. 2 2
      src/components/ui/UiTab.vue
  32. 53 0
      src/composable/theme.js
  33. 1 1
      src/content/element-selector/App.vue
  34. 1 0
      src/locales/en/newtab.json
  35. 22 1
      src/newtab/App.vue
  36. 13 4
      src/newtab/pages/Home.vue
  37. 1 1
      src/newtab/pages/Logs.vue
  38. 1 1
      src/newtab/pages/Settings.vue
  39. 2 2
      src/newtab/pages/Workflows.vue
  40. 2 2
      src/newtab/pages/collections/[id].vue
  41. 9 9
      src/newtab/pages/logs/[id].vue
  42. 3 3
      src/newtab/pages/settings/About.vue
  43. 14 0
      src/newtab/pages/settings/index.vue
  44. 12 9
      src/newtab/pages/workflows/[id].vue
  45. 5 5
      src/utils/shared.js
  46. 12 3
      tailwind.config.js

+ 2 - 3
src/assets/css/drawflow.css

@@ -63,17 +63,16 @@
   position: relative;
   width: 18px;
   height: 18px;
-  background: #fff;
   border-radius: 50%;
   cursor: crosshair;
   z-index: 1;
   margin-bottom: 5px;
-  border-color: theme('colors.accent');
   border-width: 3px;
+  @apply border-accent bg-white dark:bg-gray-900;
 }
 
 .drawflow .drawflow-node .input {
-  @apply bg-accent;
+  @apply bg-accent !important;
 }
 
 .drawflow .icon-ui {

+ 16 - 1
src/assets/css/tailwind.css

@@ -2,6 +2,21 @@
 @tailwind components;
 @tailwind utilities;
 
+:host, :root {
+  --color-primary: 59 130 246;
+  --color-secondary: 96 165 250;
+  --color-accent: 24 24 27;
+}
+.dark {
+  --color-primary: 96 165 250;
+  --color-secondary: 59 130 246;
+  --color-accent: 244 244 245;
+}
+
+* {
+  @apply dark:border-gray-700;
+}
+
 :host, body {
   font-family: 'Inter var';
   font-size: 16px;
@@ -48,7 +63,7 @@ pre {
   background: transparent;
 }
 .tippy-box[data-theme~='tooltip-theme'] {
-  @apply px-2 py-1 bg-gray-900 text-sm text-gray-200 rounded-md;
+  @apply px-2 py-1 bg-gray-900 dark:bg-gray-200 dark:text-black text-sm text-gray-200 rounded-md;
 }
 .Vue-Toastification__toast {
   font-family: inherit !important;

TEMPAT SAMPAH
src/assets/images/tile-white.png


+ 1 - 1
src/background/workflow-engine/blocks-handler/handler-loop-data.js

@@ -13,7 +13,7 @@ function loopData(block) {
       let currentLoopData;
 
       if (data.loopThrough === 'numbers') {
-        currentLoopData = this.referenceData.loopData[data.loopId] + 1;
+        currentLoopData = this.referenceData.loopData[data.loopId].data + 1;
       } else {
         currentLoopData = this.loopList[data.loopId].data[index];
       }

+ 4 - 2
src/components/block/BlockBase.vue

@@ -3,14 +3,16 @@
     <slot name="prepend" />
     <div
       :class="contentClass"
-      class="z-10 bg-white relative rounded-lg overflow-hidden w-full p-4"
+      class="z-10 bg-white dark:bg-gray-800 relative rounded-lg overflow-hidden w-full p-4"
     >
       <slot></slot>
     </div>
     <div
       class="absolute bottom-1 transition-transform duration-300 pt-4 ml-1 menu"
     >
-      <div class="bg-accent px-3 py-2 text-white rounded-lg flex items-center">
+      <div
+        class="bg-accent dark:bg-gray-100 dark:text-black px-3 py-2 text-white rounded-lg flex items-center"
+      >
         <button v-if="!hideEdit" @click="$emit('edit')">
           <v-remixicon size="20" name="riPencilLine" />
         </button>

+ 3 - 3
src/components/block/BlockBasic.vue

@@ -10,7 +10,7 @@
   >
     <span
       :class="block.category.color"
-      class="inline-block p-2 mr-2 rounded-lg bg-green-200"
+      class="inline-block p-2 mr-2 rounded-lg dark:text-black"
     >
       <v-remixicon :name="block.details.icon || 'riGlobalLine'" />
     </span>
@@ -21,7 +21,7 @@
       >
         {{ t(`workflow.blocks.${block.details.id}.name`) }}
       </p>
-      <p class="text-gray-600 text-overflow leading-tight">
+      <p class="text-gray-600 dark:text-gray-200 text-overflow leading-tight">
         {{ block.data.description }}
       </p>
       <input
@@ -36,7 +36,7 @@
         v-if="block.details.id !== 'trigger'"
         :title="t('workflow.blocks.base.moveToGroup')"
         draggable="true"
-        class="bg-white invisible move-to-group z-50 absolute -top-2 -right-2 rounded-md p-1 shadow-md"
+        class="bg-white dark:bg-gray-700 invisible move-to-group z-50 absolute -top-2 -right-2 rounded-md p-1 shadow-md"
         @dragstart="handleStartDrag"
         @mousedown.stop
       >

+ 2 - 2
src/components/block/BlockConditions.vue

@@ -3,7 +3,7 @@
     <div class="flex items-center">
       <div
         :class="block.category.color"
-        class="inline-block text-sm mr-4 p-2 rounded-lg"
+        class="inline-block text-sm mr-4 p-2 rounded-lg dark:text-black"
       >
         <v-remixicon name="riAB" size="20" class="inline-block mr-1" />
         <span>{{ t('workflow.blocks.conditions.name') }}</span>
@@ -45,7 +45,7 @@
       </div>
       <p
         v-if="block.data.conditions && block.data.conditions.length !== 0"
-        class="text-right text-gray-600"
+        class="text-right text-gray-600 dark:text-gray-200"
       >
         <span :title="t('workflow.blocks.conditions.fallbackTitle')">
           &#9432;

+ 2 - 2
src/components/block/BlockElementExists.vue

@@ -7,7 +7,7 @@
   >
     <div
       :class="block.category.color"
-      class="inline-block text-sm mb-2 p-2 rounded-lg"
+      class="inline-block text-sm mb-2 p-2 rounded-lg dark:text-black"
     >
       <v-remixicon name="riFocus3Line" size="20" class="inline-block mr-1" />
       <span>{{ t('workflow.blocks.element-exists.name') }}</span>
@@ -19,7 +19,7 @@
     >
       {{ block.data.selector || t('workflow.blocks.element-exists.selector') }}
     </p>
-    <p class="text-right text-gray-600">
+    <p class="text-right text-gray-600 dark:text-gray-200">
       <span :title="t('workflow.blocks.element-exists.fallbackTitle')">
         &#9432;
       </span>

+ 1 - 1
src/components/block/BlockGroup.vue

@@ -4,7 +4,7 @@
       <div class="flex items-center mb-2">
         <div
           :class="block.category.color"
-          class="inline-flex items-center text-sm mr-4 p-2 rounded-lg"
+          class="inline-flex items-center text-sm mr-4 p-2 rounded-lg dark:text-black"
         >
           <v-remixicon
             :name="block.details.icon || 'riFolderZipLine'"

+ 1 - 1
src/components/block/BlockLoopBreakpoint.vue

@@ -3,7 +3,7 @@
     <div class="flex items-center mb-2">
       <div
         :class="block.category.color"
-        class="inline-block text-sm mr-4 p-2 rounded-lg"
+        class="inline-block text-sm mr-4 p-2 rounded-lg dark:text-black"
       >
         <v-remixicon name="riStopLine" size="20" class="inline-block mr-1" />
         <span>{{ t('workflow.blocks.loop-breakpoint.name') }}</span>

+ 5 - 5
src/components/block/BlockRepeatTask.vue

@@ -3,7 +3,7 @@
     <div class="flex items-center mb-2">
       <div
         :class="block.category.color"
-        class="inline-block text-sm mr-4 p-2 rounded-lg"
+        class="inline-block text-sm mr-4 p-2 rounded-lg dark:text-black"
       >
         <v-remixicon name="riRepeat2Line" size="20" class="inline-block mr-1" />
         <span>{{ t('workflow.blocks.repeat-task.name') }}</span>
@@ -26,11 +26,11 @@
         required
         @input="handleInput"
       />
-      <span class="text-gray-600">{{
-        t('workflow.blocks.repeat-task.times')
-      }}</span>
+      <span class="text-gray-600 dark:text-gray-200">
+        {{ t('workflow.blocks.repeat-task.times') }}
+      </span>
     </label>
-    <p class="text-right text-gray-600">
+    <p class="text-right text-gray-600 dark:text-gray-200">
       {{ t('workflow.blocks.repeat-task.repeatFrom') }}
     </p>
   </div>

+ 2 - 2
src/components/newtab/app/AppSidebar.vue

@@ -1,6 +1,6 @@
 <template>
   <aside
-    class="fixed flex flex-col items-center h-screen left-0 top-0 w-16 py-6 bg-white z-50"
+    class="fixed flex flex-col items-center h-screen left-0 top-0 w-16 py-6 bg-white dark:bg-gray-800 z-50"
   >
     <img
       :title="`v${extensionVersion}`"
@@ -115,6 +115,6 @@ function hoverHandler({ target }) {
   top: 0;
   height: 100%;
   width: 4px;
-  @apply bg-accent;
+  @apply bg-accent dark:bg-gray-100;
 }
 </style>

+ 4 - 2
src/components/newtab/logs/LogsFilters.vue

@@ -39,7 +39,9 @@
       </template>
       <div class="w-48">
         <p class="flex-1 mb-2 font-semibold">{{ t('log.filter.title') }}</p>
-        <p class="mb-2 text-sm text-gray-600">{{ t('log.filter.byStatus') }}</p>
+        <p class="mb-2 text-sm text-gray-600 dark:text-gray-200">
+          {{ t('log.filter.byStatus') }}
+        </p>
         <div class="grid grid-cols-2 gap-2">
           <ui-radio
             v-for="status in filterByStatus"
@@ -52,7 +54,7 @@
             {{ status.name }}
           </ui-radio>
         </div>
-        <p class="mb-1 text-sm text-gray-600 mt-3">
+        <p class="mb-1 text-sm text-gray-600 dark:text-gray-200 mt-3">
           {{ t('log.filter.byDate.title') }}
         </p>
         <ui-select

+ 3 - 3
src/components/newtab/settings/SettingsBackup.vue

@@ -1,10 +1,10 @@
 <template>
-  <div class="max-w-xl mt-8">
+  <div class="max-w-xl mt-10">
     <h2 class="font-semibold mb-2">
       {{ t('settings.backupWorkflows.title') }}
     </h2>
     <div class="flex space-x-4">
-      <div class="border p-4 rounded-lg w-6/12">
+      <div class="border dark:border-gray-700 p-4 rounded-lg w-6/12">
         <div class="text-center">
           <span class="inline-block p-4 rounded-full bg-box-transparent">
             <v-remixicon name="riDownloadLine" size="36" />
@@ -17,7 +17,7 @@
           {{ t('settings.backupWorkflows.backup.button') }}
         </ui-button>
       </div>
-      <div class="border p-4 rounded-lg w-6/12">
+      <div class="border dark:border-gray-700 p-4 rounded-lg w-6/12">
         <div class="text-center">
           <span class="inline-block p-4 rounded-full bg-box-transparent">
             <v-remixicon name="riUploadLine" size="36" />

+ 3 - 1
src/components/newtab/shared/SharedCard.vue

@@ -1,5 +1,7 @@
 <template>
-  <ui-card class="hover:ring-2 flex flex-col group hover:ring-accent">
+  <ui-card
+    class="hover:ring-2 flex flex-col group hover:ring-accent dark:hover:ring-gray-200"
+  >
     <slot name="header">
       <div class="flex items-center mb-4">
         <ui-img

+ 7 - 7
src/components/newtab/shared/SharedLogsTable.vue

@@ -1,6 +1,6 @@
 <template>
   <table>
-    <tbody class="divide-y">
+    <tbody class="divide-y dark:divide-gray-800">
       <tr v-for="log in logs" :key="log.id" class="hoverable">
         <slot name="item-prepend" :log="log" />
         <td class="text-overflow" style="min-width: 140px; max-width: 330px">
@@ -11,7 +11,7 @@
             {{ log.name }}
           </router-link>
         </td>
-        <td class="log-time">
+        <td class="log-time dark:text-gray-200">
           <v-remixicon
             :title="t('log.startedDate')"
             name="riCalendarLine"
@@ -21,7 +21,7 @@
             {{ formatDate(log.startedAt, 'relative') }}
           </span>
         </td>
-        <td class="log-time" :title="t('log.duration')">
+        <td class="log-time dark:text-gray-200" :title="t('log.duration')">
           <v-remixicon name="riTimerLine"></v-remixicon>
           <span>{{ countDuration(log.startedAt, log.endedAt) }}</span>
         </td>
@@ -29,7 +29,7 @@
           <span
             :class="statusColors[log.status]"
             :title="log.status === 'error' ? getErrorMessage(log) : null"
-            class="inline-block py-1 w-16 text-center text-sm rounded-lg"
+            class="inline-block py-1 w-16 text-center text-sm rounded-md dark:text-black"
           >
             {{ t(`logStatus.${log.status}`) }}
           </span>
@@ -54,9 +54,9 @@ defineProps({
 const { t, te } = useI18n();
 
 const statusColors = {
-  error: 'bg-red-200',
-  success: 'bg-green-200',
-  stopped: 'bg-yellow-200',
+  error: 'bg-red-200 dark:bg-red-300',
+  success: 'bg-green-200 dark:bg-green-300',
+  stopped: 'bg-yellow-200 dark:bg-yellow-300',
 };
 
 function formatDate(date, format) {

+ 1 - 1
src/components/newtab/shared/SharedWorkflowState.vue

@@ -4,7 +4,7 @@
       <div class="flex-1 text-overflow mr-4">
         <p class="w-full mr-2 text-overflow">{{ data.state.name }}</p>
         <p
-          class="w-full mr-2 text-gray-600 leading-tight text-overflow"
+          class="w-full mr-2 text-gray-600 dark:text-gray-200 leading-tight text-overflow"
           :title="`Started at: ${formatDate(
             data.state.startedTimestamp,
             'DD MMM, hh:mm A'

+ 2 - 2
src/components/newtab/workflow/WorkflowActions.vue

@@ -10,12 +10,12 @@
       <v-remixicon :name="item.icon" />
     </button>
   </ui-card>
-  <ui-card padding="p-1 ml-4">
+  <ui-card padding="p-1 ml-4 flex items-center">
     <button
       v-tooltip.group="
         t(`workflow.protect.${workflow.isProtected ? 'remove' : 'title'}`)
       "
-      :class="{ 'text-green-600': workflow.isProtected }"
+      :class="{ 'text-green-600 dark:text-green-400': workflow.isProtected }"
       class="hoverable p-2 rounded-lg"
       @click="$emit('protect')"
     >

+ 8 - 2
src/components/newtab/workflow/WorkflowBuilder.vue

@@ -9,12 +9,12 @@
     <div class="absolute z-10 p-4 bottom-0 left-0">
       <button
         v-tooltip.group="t('workflow.editor.resetZoom')"
-        class="p-2 rounded-lg bg-white mr-2"
+        class="p-2 rounded-lg bg-white dark:bg-gray-800 mr-2"
         @click="editor.zoom_reset()"
       >
         <v-remixicon name="riFullscreenLine" />
       </button>
-      <div class="rounded-lg bg-white inline-block">
+      <div class="rounded-lg bg-white dark:bg-gray-800 inline-block">
         <button
           v-tooltip.group="t('workflow.editor.zoomOut')"
           class="p-2 rounded-lg relative z-10"
@@ -449,4 +449,10 @@ export default {
   background-image: url('@/assets/images/tile.png');
   background-size: 35px;
 }
+.dark #drawflow {
+  background-image: url('@/assets/images/tile-white.png');
+}
+.drawflow .drawflow-node {
+  @apply dark:bg-gray-800;
+}
 </style>

+ 4 - 2
src/components/newtab/workflow/WorkflowDetailsCard.vue

@@ -60,7 +60,9 @@
           :class="categories[catId].color"
           class="h-3 w-3 rounded-full"
         ></span>
-        <p class="capitalize text-gray-600">{{ categories[catId].name }}</p>
+        <p class="capitalize text-gray-600 dark:text-gray-200">
+          {{ categories[catId].name }}
+        </p>
       </div>
       <div class="grid grid-cols-2 gap-2 mb-4">
         <div
@@ -84,7 +86,7 @@
             :title="t('common.docs')"
             target="_blank"
             rel="noopener"
-            class="absolute top-px right-2 top-2 text-gray-600 invisible group-hover:visible"
+            class="absolute top-px right-2 top-2 text-gray-600 dark:text-gray-300 invisible group-hover:visible"
           >
             <v-remixicon name="riInformationLine" size="18" />
           </a>

+ 1 - 1
src/components/newtab/workflow/WorkflowEditBlock.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="px-4 overflow-auto scroll pb-1">
     <div
-      class="sticky top-0 z-20 bg-white border-b border-gray-100 pb-4 mb-4 flex items-center"
+      class="sticky top-0 z-20 bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700 pb-4 mb-4 flex items-center"
     >
       <button class="mr-2" @click="$emit('close')">
         <v-remixicon name="riArrowLeftLine" />

+ 1 - 1
src/components/newtab/workflow/WorkflowRunning.vue

@@ -5,7 +5,7 @@
         <div class="flex-1 text-overflow mr-4">
           <p class="w-full mr-2 text-overflow">{{ item.state.name }}</p>
           <p
-            class="w-full mr-2 text-gray-600 leading-tight text-overflow"
+            class="w-full mr-2 text-gray-600 dark:text-gray-200 leading-tight text-overflow"
             :title="`Started at: ${formatDate(
               item.state.startedTimestamp,
               'DD MMM, hh:mm A'

+ 3 - 3
src/components/newtab/workflow/edit/EditConditions.vue

@@ -21,7 +21,7 @@
           class="py-2 px-4 w-full transition rounded-lg bg-transparent"
         />
         <button
-          class="bg-white absolute top-1/2 right-4 p-2 rounded-lg -translate-y-1/2 group-hover:right-14"
+          class="bg-white dark:bg-gray-700 absolute top-1/2 right-4 p-2 rounded-lg -translate-y-1/2 group-hover:right-14"
           @click="deleteCondition(index)"
         >
           <v-remixicon size="20" name="riDeleteBin7Line" />
@@ -29,7 +29,7 @@
         <select
           v-model="condition.type"
           :title="getTitle(index)"
-          class="bg-white absolute right-4 font-mono z-10 p-2 top-1/2 leading-tight -translate-y-1/2 text-center transition rounded-lg appearance-none"
+          class="bg-white dark:bg-gray-700 absolute right-4 font-mono z-10 p-2 top-1/2 leading-tight -translate-y-1/2 text-center transition rounded-lg appearance-none"
         >
           <option
             v-for="(name, type) in conditionTypes"
@@ -40,7 +40,7 @@
           </option>
         </select>
         <div
-          class="w-full bg-gray-300 h-px mx-auto"
+          class="w-full bg-gray-300 dark:bg-gray-700 h-px mx-auto"
           style="max-width: 89%"
         ></div>
         <input

+ 1 - 1
src/components/newtab/workflow/edit/EditNewWindow.vue

@@ -52,7 +52,7 @@
           @change="updateData({ width: +$event })"
         />
       </div>
-      <p class="mt-4 text-gray-600">
+      <p class="mt-4 text-gray-600 dark:text-gray-200">
         {{ t('workflow.blocks.new-window.note') }}
       </p>
     </template>

+ 1 - 1
src/components/newtab/workflow/edit/EditTakeScreenshot.vue

@@ -31,7 +31,7 @@
         <option value="jpeg">JPEG</option>
       </ui-select>
     </div>
-    <p class="text-sm text-gray-600 ml-2">Image quality:</p>
+    <p class="text-sm text-gray-600 dark:text-gray-200 ml-2">Image quality:</p>
     <div class="bg-box-transparent px-4 mb-2 py-2 rounded-lg flex items-center">
       <input
         :value="data.quality"

+ 2 - 2
src/components/ui/UiButton.vue

@@ -54,11 +54,11 @@ export default {
     const variants = {
       default: 'bg-input',
       accent:
-        'bg-accent hover:bg-gray-700 dark:bg-gray-200 dark:text-gray-900 text-white',
+        'bg-accent hover:bg-gray-700 dark:bg-gray-100 dark:hover:bg-gray-200 dark:text-black text-white',
       primary:
         'bg-primary text-white dark:bg-secondary dark:hover:bg-primary hover:bg-secondary',
       danger:
-        'bg-red-500 text-white dark:bg-red-600 dark:hover:bg-red-500 hover:bg-red-400',
+        'bg-red-400 text-white dark:bg-red-500 dark:hover:bg-red-500 hover:bg-red-400',
     };
 
     return {

+ 2 - 2
src/components/ui/UiCheckbox.vue

@@ -12,12 +12,12 @@
         @change="changeHandler"
       />
       <div
-        class="border rounded absolute top-0 left-0 bg-input checkbox-ui__mark cursor-pointer"
+        class="border dark:border-gray-700 rounded absolute top-0 left-0 bg-input checkbox-ui__mark cursor-pointer"
       >
         <v-remixicon
           name="riCheckLine"
           size="20"
-          class="text-white"
+          class="text-white dark:text-black"
         ></v-remixicon>
       </div>
     </div>

+ 1 - 1
src/components/ui/UiModal.vue

@@ -7,7 +7,7 @@
       <transition name="modal" mode="out-in">
         <div
           v-if="show"
-          class="bg-black p-5 overflow-y-auto bg-opacity-20 modal-ui__content-container z-50 flex justify-center items-end md:items-center"
+          class="bg-black p-5 overflow-y-auto bg-opacity-20 dark:bg-opacity-60 modal-ui__content-container z-50 flex justify-center items-end md:items-center"
           :style="{ 'backdrop-filter': blur && 'blur(2px)' }"
           @click.self="closeModal"
         >

+ 1 - 1
src/components/ui/UiPopover.vue

@@ -9,7 +9,7 @@
     </div>
     <div
       ref="content"
-      class="ui-popover__content bg-white dark:bg-gray-800 rounded-lg shadow-xl border"
+      class="ui-popover__content bg-white dark:bg-gray-800 rounded-lg shadow-xl border dark:border-gray-700"
       :class="[padding]"
     >
       <slot v-bind="{ isShow }"></slot>

+ 1 - 0
src/components/ui/UiSwitch.vue

@@ -71,6 +71,7 @@ export default {
 }
 
 .ui-switch input:checked ~ .ui-switch__ball {
+  @apply dark:bg-gray-900;
   background-color: white;
   left: calc(100% - 21px);
 }

+ 2 - 2
src/components/ui/UiTab.vue

@@ -4,8 +4,8 @@
     :class="[
       uiTabs.small.value ? 'p-2' : 'py-3 px-2',
       uiTabs.modelValue.value === value
-        ? 'border-accent text-gray-800 dark:text-white'
-        : 'border-transparent',
+        ? 'border-accent dark:border-gray-100 text-gray-800 dark:text-white'
+        : '!border-transparent',
       { 'flex-1': uiTabs.fill.value },
     ]"
     :tabIndex="uiTabs.modelValue.value === value ? 0 : -1"

+ 53 - 0
src/composable/theme.js

@@ -0,0 +1,53 @@
+import { ref, onMounted } from 'vue';
+import browser from 'webextension-polyfill';
+
+const themes = [
+  { name: 'Light', id: 'light' },
+  { name: 'Dark', id: 'dark' },
+  { name: 'System', id: 'system' },
+];
+const isPreferDark = () =>
+  window.matchMedia('(prefers-color-scheme: dark)').matches;
+
+export function useTheme() {
+  const activeTheme = ref('system');
+
+  async function setTheme(theme) {
+    const isValidTheme = themes.some(({ id }) => id === theme);
+
+    if (!isValidTheme) return;
+
+    let isDarkTheme = theme === 'dark';
+
+    if (theme === 'system') isDarkTheme = isPreferDark();
+
+    document.documentElement.classList.toggle('dark', isDarkTheme);
+    activeTheme.value = theme;
+
+    await browser.storage.local.set({ theme });
+  }
+  async function getTheme() {
+    let { theme } = await browser.storage.local.get('theme');
+
+    if (!theme) theme = 'system';
+
+    return theme;
+  }
+  async function init() {
+    const theme = await getTheme();
+
+    await setTheme(theme);
+  }
+
+  onMounted(async () => {
+    activeTheme.value = await getTheme();
+  });
+
+  return {
+    init,
+    themes,
+    activeTheme,
+    set: setTheme,
+    get: getTheme,
+  };
+}

+ 1 - 1
src/content/element-selector/App.vue

@@ -4,7 +4,7 @@
       'select-none': state.isDragging,
       'bg-black bg-opacity-30': !state.hide,
     }"
-    class="root fixed h-full w-full pointer-events-none top-0 text-gray-900 left-0"
+    class="root fixed h-full w-full pointer-events-none top-0 text-black left-0"
     style="z-index: 9999999999; font-family: Inter, sans-serif; font-size: 16px"
   >
     <div

+ 1 - 0
src/locales/en/newtab.json

@@ -12,6 +12,7 @@
     "text2": "see what's new."
   },
   "settings": {
+    "theme": "Theme",
     "language": {
       "label": "Language",
       "helpTranslate": "Can't find your language? Help translate.",

+ 22 - 1
src/newtab/App.vue

@@ -33,11 +33,15 @@ import { useStore } from 'vuex';
 import { useI18n } from 'vue-i18n';
 import { compare } from 'compare-versions';
 import browser from 'webextension-polyfill';
+import { useTheme } from '@/composable/theme';
 import { loadLocaleMessages, setI18nLanguage } from '@/lib/vue-i18n';
 import AppSidebar from '@/components/newtab/app/AppSidebar.vue';
 
-const store = useStore();
 const { t } = useI18n();
+const store = useStore();
+const theme = useTheme();
+
+theme.init();
 
 const retrieved = ref(false);
 
@@ -91,3 +95,20 @@ onMounted(async () => {
   localStorage.setItem('ext-version', currentVersion);
 });
 </script>
+<style>
+html,
+body {
+  @apply bg-gray-50 dark:bg-gray-900 text-black dark:text-gray-100;
+}
+body {
+  min-height: 100vh;
+}
+#app {
+  height: 100%;
+}
+h1,
+h2,
+h3 {
+  @apply dark:text-white;
+}
+</style>

+ 13 - 4
src/newtab/pages/Home.vue

@@ -4,7 +4,10 @@
     <div class="flex items-start">
       <div class="w-8/12 mr-8">
         <div class="grid gap-4 mb-8 2xl:grid-cols-4 grid-cols-3">
-          <p v-if="workflows.length === 0" class="text-center text-gray-600">
+          <p
+            v-if="workflows.length === 0"
+            class="text-center text-gray-600 dark:text-gray-200"
+          >
             {{ t('message.noData') }}
           </p>
           <shared-card
@@ -22,19 +25,25 @@
             <p class="font-semibold inline-block">Logs</p>
             <router-link
               to="/logs"
-              class="text-gray-600 text-sm dark:text-gray-200"
+              class="text-gray-600 dark:text-gray-200 text-sm"
             >
               {{ t('home.viewAll') }}
             </router-link>
           </div>
-          <p v-if="logs.length === 0" class="text-center text-gray-600">
+          <p
+            v-if="logs.length === 0"
+            class="text-center text-gray-600 dark:text-gray-200"
+          >
             {{ t('message.noData') }}
           </p>
           <shared-logs-table :logs="logs" class="w-full" />
         </div>
       </div>
       <div class="w-4/12 space-y-4">
-        <p v-if="workflowState.length === 0" class="text-center text-gray-600">
+        <p
+          v-if="workflowState.length === 0"
+          class="text-center text-gray-600 dark:text-gray-200"
+        >
           {{ t('message.noData') }}
         </p>
         <shared-workflow-state

+ 1 - 1
src/newtab/pages/Logs.vue

@@ -32,7 +32,7 @@
               />
               <v-remixicon
                 name="riDeleteBin7Line"
-                class="text-red-500 cursor-pointer"
+                class="text-red-500 dark:text-red-400 cursor-pointer"
                 @click="deleteLog(log.id)"
               />
             </div>

+ 1 - 1
src/newtab/pages/Settings.vue

@@ -15,7 +15,7 @@
             :class="[
               isExactActive
                 ? 'bg-box-transparent'
-                : 'text-gray-600 dark:text-gray-600',
+                : 'text-gray-600 dark:text-gray-200',
             ]"
             tag="a"
             @click="navigate"

+ 2 - 2
src/newtab/pages/Workflows.vue

@@ -16,7 +16,7 @@
       <div class="flex items-center workflow-sort">
         <ui-button
           icon
-          class="rounded-r-none border-gray-300 border-r"
+          class="rounded-r-none border-gray-300 dark:border-gray-700 border-r"
           @click="state.sortOrder = state.sortOrder === 'asc' ? 'desc' : 'asc'"
         >
           <v-remixicon
@@ -106,7 +106,7 @@
             <v-remixicon
               v-if="workflow.isProtected"
               name="riShieldKeyholeLine"
-              class="text-green-600 ml-2"
+              class="text-green-600 dark:text-green-400 ml-2"
             />
             <ui-popover v-if="!workflow.isProtected" class="h-6 ml-2">
               <template #trigger>

+ 2 - 2
src/newtab/pages/collections/[id].vue

@@ -51,7 +51,7 @@
       </div>
       <div class="flex-1 relative">
         <div class="flex items-center mb-4">
-          <div class="px-1 inline-block rounded-lg bg-white">
+          <div class="px-1 inline-block rounded-lg bg-white dark:bg-gray-800">
             <ui-tabs
               v-model="state.activeTab"
               class="border-none h-full space-x-1"
@@ -167,7 +167,7 @@
               <p class="leading-tight">
                 {{ t('collection.options.atOnce.title') }}
               </p>
-              <p class="text-sm text-gray-600 leading-tight">
+              <p class="text-sm text-gray-600 dark:text-gray-200 leading-tight">
                 {{ t('collection.options.atOnce.description') }}
               </p>
             </ui-checkbox>

+ 9 - 9
src/newtab/pages/logs/[id].vue

@@ -5,7 +5,7 @@
         <h1 class="text-2xl max-w-md text-overflow font-semibold">
           {{ activeLog.name }}
         </h1>
-        <p class="text-gray-600">
+        <p class="text-gray-600 dark:text-gray-200">
           {{
             t(`log.description.text`, {
               status: t(`log.description.status.${activeLog.status}`),
@@ -16,7 +16,7 @@
         </p>
       </div>
       <div class="flex-grow"></div>
-      <ui-button class="text-red-500" @click="deleteLog">
+      <ui-button class="text-red-500 dark:text-red-400" @click="deleteLog">
         {{ t('common.delete') }}
       </ui-button>
     </div>
@@ -35,7 +35,7 @@
           <ui-list-item v-for="(item, index) in history" :key="index">
             <span
               :class="logsType[item.type]?.color"
-              class="p-1 rounded-lg align-middle inline-block mr-2"
+              class="p-1 rounded-lg align-middle inline-block mr-2 dark:text-black"
             >
               <v-remixicon :name="logsType[item.type]?.icon" size="20" />
             </span>
@@ -59,7 +59,7 @@
             >
               <v-remixicon name="riExternalLinkLine" />
             </router-link>
-            <p class="text-gray-600">
+            <p class="text-gray-600 dark:text-gray-200">
               {{ countDuration(0, item.duration || 0) }}
             </p>
           </ui-list-item>
@@ -112,23 +112,23 @@ import LogsDataViewer from '@/components/newtab/logs/LogsDataViewer.vue';
 
 const logsType = {
   success: {
-    color: 'bg-green-200',
+    color: 'bg-green-200 dark:bg-green-400',
     icon: 'riCheckLine',
   },
   stop: {
-    color: 'bg-yellow-200',
+    color: 'bg-yellow-200 dark:bg-yellow-400',
     icon: 'riStopLine',
   },
   stopped: {
-    color: 'bg-yellow-200',
+    color: 'bg-yellow-200 dark:bg-yellow-400',
     icon: 'riStopLine',
   },
   error: {
-    color: 'bg-red-200',
+    color: 'bg-red-200 dark:bg-red-400',
     icon: 'riErrorWarningLine',
   },
   finish: {
-    color: 'bg-blue-200',
+    color: 'bg-blue-200 dark:bg-blue-400',
     icon: 'riFlagLine',
   },
 };

+ 3 - 3
src/newtab/pages/settings/About.vue

@@ -5,7 +5,7 @@
     </div>
     <p class="text-2xl font-semibold">Automa</p>
     <p class="mb-2 mt-1">Version: {{ extensionVersion }}</p>
-    <p class="text-gray-600">
+    <p class="text-gray-600 dark:text-gray-200">
       Automa is a chrome extension for browser automation. From auto-fill forms,
       doing a repetitive task, taking a screenshot, to scraping data of the
       website, it's up to you what you want to do with this extension.
@@ -23,9 +23,9 @@
         <p class="ml-1 hidden">{{ link.name }}</p>
       </a>
     </div>
-    <div class="border-b my-8"></div>
+    <div class="border-b dark:border-gray-700 my-8"></div>
     <h2 class="text-xl font-semibold">Contributors</h2>
-    <p class="mt-1 text-gray-600">
+    <p class="mt-1 text-gray-600 dark:text-gray-200">
       Thanks to everyone who has submitted issues, made suggestions, and
       generally helped make this a better project.
     </p>

+ 14 - 0
src/newtab/pages/settings/index.vue

@@ -1,4 +1,16 @@
 <template>
+  <div class="mb-8">
+    <p class="font-semibold mb-1">{{ t('settings.theme') }}</p>
+    <ui-select
+      :model-value="theme.activeTheme.value"
+      class="w-80"
+      @change="theme.set($event)"
+    >
+      <option v-for="item in theme.themes" :key="item.id" :value="item.id">
+        {{ item.name }}
+      </option>
+    </ui-select>
+  </div>
   <div class="flex items-center">
     <div id="languages">
       <p class="font-semibold mb-1">{{ t('settings.language.label') }}</p>
@@ -34,11 +46,13 @@
 import { computed, ref } from 'vue';
 import { useStore } from 'vuex';
 import { useI18n } from 'vue-i18n';
+import { useTheme } from '@/composable/theme';
 import { supportLocales } from '@/utils/shared';
 import SettingsBackup from '@/components/newtab/settings/SettingsBackup.vue';
 
 const { t } = useI18n();
 const store = useStore();
+const theme = useTheme();
 
 const isLangChange = ref(false);
 const settings = computed(() => store.state.settings);

+ 12 - 9
src/newtab/pages/workflows/[id].vue

@@ -1,19 +1,23 @@
 <template>
-  <div v-if="protectionState.needed" class="my-12 mx-auto max-w-md w-full">
-    <div class="inline-block p-4 bg-green-200 mb-4 rounded-full">
+  <div v-if="protectionState.needed" class="py-12 mx-auto max-w-md w-full">
+    <div
+      class="inline-block p-4 bg-green-200 dark:bg-green-400 mb-4 rounded-full"
+    >
       <v-remixicon name="riShieldKeyholeLine" size="52" />
     </div>
-    <h1 class="text-2xl font-semibold">
+    <h1 class="text-xl dark:text-gray-100 font-semibold">
       {{ t('workflow.locked.title') }}
     </h1>
-    <p class="text-gray-600 text-lg">{{ t('workflow.locked.body') }}</p>
+    <p class="text-gray-600 dark:text-gray-200">
+      {{ t('workflow.locked.body') }}
+    </p>
     <form class="flex items-center mt-6" @submit.prevent="unlockWorkflow">
       <ui-input
         v-model="protectionState.password"
         :placeholder="t('common.password')"
         :type="protectionState.showPassword ? 'text' : 'password'"
         autofocus
-        class="flex-1 mr-4"
+        class="w-80 mr-4"
       >
         <template #append>
           <v-remixicon
@@ -36,7 +40,7 @@
   <div v-else class="flex h-screen">
     <div
       v-if="state.showSidebar"
-      class="w-80 bg-white py-6 relative border-l border-gray-100 flex flex-col"
+      class="w-80 bg-white dark:bg-gray-800 py-6 relative border-l border-gray-100 dark:border-gray-700 dark:border-opacity-50 flex flex-col"
     >
       <workflow-edit-block
         v-if="state.isEditBlock"
@@ -54,7 +58,7 @@
       <div class="absolute w-full flex items-center z-10 left-0 p-4 top-0">
         <ui-tabs
           v-model="activeTab"
-          class="border-none px-2 rounded-lg h-full space-x-1 bg-white"
+          class="border-none px-2 rounded-lg h-full space-x-1 bg-white dark:bg-gray-800"
         >
           <button
             v-tooltip="
@@ -62,7 +66,6 @@
                 shortcut['editor:toggle-sidebar'].readable
               })`
             "
-            class="text-gray-800"
             style="margin-right: 6px"
             @click="toggleSidebar"
           >
@@ -122,7 +125,7 @@
                 <td class="text-right">
                   <v-remixicon
                     name="riDeleteBin7Line"
-                    class="inline-block text-red-500 cursor-pointer"
+                    class="inline-block text-red-500 cursor-pointer dark:text-red-400"
                     @click="deleteLog(itemLog.id)"
                   />
                 </td>

+ 5 - 5
src/utils/shared.js

@@ -639,23 +639,23 @@ export const tasks = {
 export const categories = {
   interaction: {
     name: 'Web interaction',
-    color: 'bg-green-200',
+    color: 'bg-green-200 dark:bg-green-300',
   },
   browser: {
     name: 'Browser',
-    color: 'bg-orange-200',
+    color: 'bg-orange-200 dark:bg-orange-300',
   },
   general: {
     name: 'General',
-    color: 'bg-yellow-200',
+    color: 'bg-yellow-200 dark:bg-yellow-300',
   },
   onlineServices: {
     name: 'Online services',
-    color: 'bg-red-200',
+    color: 'bg-red-200 dark:bg-red-300',
   },
   conditions: {
     name: 'Conditions',
-    color: 'bg-blue-200',
+    color: 'bg-blue-200 dark:bg-blue-300',
   },
 };
 

+ 12 - 3
tailwind.config.js

@@ -1,14 +1,23 @@
 const colors = require('tailwindcss/colors');
 
+function withOpacityValue(variable) {
+  return ({ opacityValue }) => {
+    if (opacityValue === undefined) {
+      return `rgb(var(${variable}))`;
+    }
+    return `rgb(var(${variable}) / ${opacityValue})`;
+  };
+}
+
 module.exports = {
   content: ['./src/**/*.{js,jsx,ts,tsx,vue}'],
   darkMode: 'class', // or 'media' or 'class'
   theme: {
     extend: {
       colors: {
-        primary: colors.blue['500'],
-        secondary: colors.blue['400'],
-        accent: colors.zinc['900'],
+        primary: withOpacityValue('--color-primary'),
+        secondary: withOpacityValue('--color-secondary'),
+        accent: withOpacityValue('--color-accent'),
         gray: colors.zinc,
         orange: colors.orange,
       },