diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 9dad962e0a..1b50160496 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -20,7 +20,7 @@ Fixes # (issue)
## Screenshots & recording
-
+
## Manual review steps
diff --git a/app/assets/svgs/moon-plus-plus.svg b/app/assets/svgs/moon-plus-plus.svg
new file mode 100644
index 0000000000..f1a19d71e6
--- /dev/null
+++ b/app/assets/svgs/moon-plus-plus.svg
@@ -0,0 +1 @@
+
diff --git a/app/components/avo/field_wrapper_component.html.erb b/app/components/avo/field_wrapper_component.html.erb
index e93c94b6ee..c3287e9ce2 100644
--- a/app/components/avo/field_wrapper_component.html.erb
+++ b/app/components/avo/field_wrapper_component.html.erb
@@ -2,13 +2,13 @@
class: classes,
style: style,
data: data do %>
- <%= content_tag :div, class: class_names("pt-4 flex self-start items-center flex-shrink-0 w-48 px-6 uppercase font-semibold text-gray-500 text-sm", @field.get_html(:classes, view: view, element: :label), {
- "md:pt-4 md:w-full": stacked?,
- "h-full md:pt-0": !stacked?,
- "md:h-10 ": !stacked? && short?,
- "md:h-14 ": !stacked? && !short?,
- "md:w-48 xl:w-64": compact?,
- "md:w-64": !compact?,
+ <%= content_tag :div, class: class_names("py-field-wrapper-y flex self-start items-center flex-shrink-0 w-48 px-field-wrapper-x uppercase font-semibold text-gray-500 text-sm", @field.get_html(:classes, view: view, element: :label), {
+ # "md:pt-4 md:w-full": stacked?,
+ # "h-full md:pt-0": !stacked?,
+ # "md:h-10 ": !stacked? && short?,
+ # "md:h-14 ": !stacked? && !short?,
+ # "md:w-48 xl:w-64": compact?,
+ # "md:w-64": !compact?,
}), data: {slot: "label"} do %>
<% if form.present? %>
<%= form.label field.id, label %>
@@ -17,7 +17,7 @@
<% end %>
<% if on_edit? && field.is_required? %> * <% end %>
<% end %>
- <%= content_tag :div, class: class_names("flex-1 flex flex-row md:min-h-inherit py-2 px-6", @field.get_html(:classes, view: view, element: :content), {
+ <%= content_tag :div, class: class_names("flex-1 flex flex-row md:min-h-inherit py-field-wrapper px-6", @field.get_html(:classes, view: view, element: :content), {
"pb-4": stacked?,
}), data: {slot: "value"} do %>
diff --git a/app/components/avo/field_wrapper_component.rb b/app/components/avo/field_wrapper_component.rb
index d777aa090c..79ef737697 100644
--- a/app/components/avo/field_wrapper_component.rb
+++ b/app/components/avo/field_wrapper_component.rb
@@ -44,7 +44,7 @@ def initialize(
end
def classes(extra_classes = "")
- "field-wrapper relative flex flex-col grow pb-2 md:pb-0 leading-tight min-h-14 h-full #{stacked? ? "field-wrapper-layout-stacked" : "field-wrapper-layout-inline md:flex-row md:items-center"} #{compact? ? "field-wrapper-size-compact" : "field-wrapper-size-regular"} #{full_width? ? "field-width-full" : "field-width-regular"} #{@classes || ""} #{extra_classes || ""} #{@field.get_html(:classes, view: view, element: :wrapper)}"
+ "field-wrapper relative flex flex-col grow pb-2 md:pb-0 leading-tight h-full #{stacked? ? "field-wrapper-layout-stacked" : "field-wrapper-layout-inline md:flex-row md:items-center"} #{compact? ? "field-wrapper-size-compact" : "field-wrapper-size-regular"} #{full_width? ? "field-width-full" : "field-width-regular"} #{@classes || ""} #{extra_classes || ""} #{@field.get_html(:classes, view: view, element: :wrapper)}"
end
def style
diff --git a/app/components/avo/index/field_wrapper_component.rb b/app/components/avo/index/field_wrapper_component.rb
index 0d16284c66..621a28bea4 100644
--- a/app/components/avo/index/field_wrapper_component.rb
+++ b/app/components/avo/index/field_wrapper_component.rb
@@ -18,7 +18,7 @@ def classes
result = @classes
unless @flush
- result += " py-3"
+ result += " py-index-field-wrapper"
end
result += " #{@field.get_html(:classes, view: view, element: :wrapper)}"
diff --git a/app/controllers/avo/application_controller.rb b/app/controllers/avo/application_controller.rb
index 2434ba74f3..e25d6ab94f 100644
--- a/app/controllers/avo/application_controller.rb
+++ b/app/controllers/avo/application_controller.rb
@@ -25,6 +25,7 @@ class ApplicationController < ::ActionController::Base
before_action :set_view
before_action :set_sidebar_open
before_action :set_stylesheet_assets_path
+ before_action :set_color_scheme
rescue_from Avo::NotAuthorizedError, with: :render_unauthorized
rescue_from ActiveRecord::RecordInvalid, with: :exception_logger
@@ -312,5 +313,9 @@ def set_stylesheet_assets_path
"avo.base"
end
end
+
+ def set_color_scheme
+ @color_scheme = cookies[:color_scheme] || "light"
+ end
end
end
diff --git a/app/controllers/avo/color_schemes_controller.rb b/app/controllers/avo/color_schemes_controller.rb
new file mode 100644
index 0000000000..7b638f52a2
--- /dev/null
+++ b/app/controllers/avo/color_schemes_controller.rb
@@ -0,0 +1,17 @@
+require_dependency "avo/application_controller"
+
+module Avo
+ class ColorSchemesController < ApplicationController
+ def create
+ return unless params[:color_scheme].in?(["auto", "light", "dark"])
+
+ cookies[:color_scheme] = if params[:color_scheme] == "auto"
+ nil
+ else
+ params[:color_scheme]
+ end
+
+ render turbo_stream: turbo_stream.reload
+ end
+ end
+end
diff --git a/app/controllers/avo/theme_options_controller.rb b/app/controllers/avo/theme_options_controller.rb
new file mode 100644
index 0000000000..3319312a4e
--- /dev/null
+++ b/app/controllers/avo/theme_options_controller.rb
@@ -0,0 +1,7 @@
+module Avo
+ class ThemeOptionsController < ApplicationController
+ def update
+ puts 'updating'.inspect
+ end
+ end
+end
diff --git a/app/helpers/avo/application_helper.rb b/app/helpers/avo/application_helper.rb
index e0f826b533..767c3b0ff0 100644
--- a/app/helpers/avo/application_helper.rb
+++ b/app/helpers/avo/application_helper.rb
@@ -85,7 +85,7 @@ def input_classes(extra_classes = "", has_error: false)
end
def white_panel_classes
- "bg-white rounded shadow-md"
+ "bg-white rounded-panel shadow-md"
end
def get_model_class(model)
diff --git a/app/javascript/avo.base.js b/app/javascript/avo.base.js
index 5428444e3a..63f23d844c 100644
--- a/app/javascript/avo.base.js
+++ b/app/javascript/avo.base.js
@@ -95,6 +95,13 @@ document.addEventListener('turbo:before-cache', () => {
document.querySelectorAll('[data-turbo-remove-before-cache]').forEach((element) => element.remove())
})
+// Watch for live changes when the user has "auto" as the default setting.
+window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {
+ const method = event.matches ? 'add' : 'remove'
+
+ document.documentElement.classList[method]('dark')
+})
+
window.Avo = window.Avo || { configuration: {} }
window.Avo.menus = {
diff --git a/app/javascript/js/controllers.js b/app/javascript/js/controllers.js
index 0e3aa0c89f..8d59509309 100644
--- a/app/javascript/js/controllers.js
+++ b/app/javascript/js/controllers.js
@@ -12,7 +12,6 @@ import DashboardCardController from './controllers/dashboard_card_controller'
import DateFieldController from './controllers/fields/date_field_controller'
import EasyMdeController from './controllers/fields/easy_mde_controller'
import FilterController from './controllers/filter_controller'
-import PanelRefreshController from './controllers/fields/panel_refresh_controller'
import HiddenInputController from './controllers/hidden_input_controller'
import InputAutofocusController from './controllers/input_autofocus_controller'
import ItemSelectAllController from './controllers/item_select_all_controller'
@@ -22,6 +21,7 @@ import LoadingButtonController from './controllers/loading_button_controller'
import MenuController from './controllers/menu_controller'
import ModalController from './controllers/modal_controller'
import MultipleSelectFilterController from './controllers/multiple_select_filter_controller'
+import PanelRefreshController from './controllers/fields/panel_refresh_controller'
import PerPageController from './controllers/per_page_controller'
import PreviewController from './controllers/preview_controller'
import ProgressBarFieldController from './controllers/fields/progress_bar_field_controller'
@@ -37,6 +37,7 @@ import SidebarController from './controllers/sidebar_controller'
import TabsController from './controllers/tabs_controller'
import TagsFieldController from './controllers/fields/tags_field_controller'
import TextFilterController from './controllers/text_filter_controller'
+import ThemeOptionsController from './controllers/theme_options_controller'
import TippyController from './controllers/tippy_controller'
import ToggleController from './controllers/toggle_controller'
import TrixFieldController from './controllers/fields/trix_field_controller'
@@ -70,6 +71,7 @@ application.register('self-destroy', SelfDestroyController)
application.register('sidebar', SidebarController)
application.register('tabs', TabsController)
application.register('text-filter', TextFilterController)
+application.register('theme-options', ThemeOptionsController)
application.register('tippy', TippyController)
application.register('toggle', ToggleController)
diff --git a/app/javascript/js/controllers/theme_options_controller.js b/app/javascript/js/controllers/theme_options_controller.js
new file mode 100644
index 0000000000..17d48155b5
--- /dev/null
+++ b/app/javascript/js/controllers/theme_options_controller.js
@@ -0,0 +1,141 @@
+import { Controller } from '@hotwired/stimulus'
+// import { put } from '@rails/request.js'
+import { put } from '@rails/request.js'
+import kebabCase from 'lodash/kebabCase'
+
+export default class extends Controller {
+ static targets = ['label']
+
+ static values = {
+ path: String,
+ }
+
+ cssVariables = ['borderRadiusPanel', 'colorNeutral', 'colorPrimary', 'strokeWidth', 'density']
+
+ borderRadius = 1.25
+
+ strokeWidth = 1
+
+ colorPrimary = '#0586DD'
+
+ colorNeutral = '#333'
+
+ density = 0
+
+ // Value in rgb
+ // Returns the final color value
+ colorPrimaryValue() {
+ let result = '';
+
+ // TODO: compute the colors dynamically
+ [50, 100, 150, 200, 300, 400, 500, 600, 700, 800, 850, 900].forEach((key) => {
+ result += this.#outputLine(`colorPrimary${key}`, this.colorPrimary)
+ })
+
+ return result
+ }
+
+ // Value in rgb
+ // Returns the final color value
+ colorNeutralValue() {
+ return this.#outputLine('colorNeutral', this.colorNeutral)
+ }
+
+ // Returns value in rem
+ borderRadiusPanelValue() {
+ const value = this.borderRadius * 1
+
+ // return `${value}rem`
+ return this.#outputLine('borderRadiusPanel', `${value}rem`)
+ }
+
+ // Returns value in rem
+ strokeWidthValue() {
+ const value = this.strokeWidth * 1
+
+ // return `${value}rem`
+ return this.#outputLine('strokeWidth', `${value}rem`)
+ }
+
+ // Returns value in rem
+ densityValue() {
+ const result = []
+
+ // const scale = {
+ // '-3': 0,
+ // '-2': 0.25,
+ // '-1': 0.5,
+ // 0: 0.75,
+ // 1: 1,
+ // 2: 1.25,
+ // 3: 1.5,
+ // }
+ const paddingXScale = {
+ '-4': 0,
+ '-3': 0.5,
+ '-2': 1,
+ '-1': 1.25,
+ 0: 1.5,
+ 1: 1.75,
+ 2: 2,
+ // 3: 1.75,
+ // 4: 2,
+ }
+ const paddingYScale = {
+ '-4': 0,
+ '-3': 0.25,
+ '-2': 0.5,
+ '-1': 0.75,
+ 0: 1,
+ 1: 1.25,
+ 2: 1.5,
+ // 3: 1.75,
+ // 4: 2,
+ }
+ const value = paddingYScale[this.density]
+ console.log(this.density, value)
+
+ result.push(this.#outputLine('padding-index-field-wrapper', `${value}rem`))
+ result.push(this.#outputLine('padding-field-wrapper-y', `${value}rem`))
+ result.push(this.#outputLine('padding-field-wrapper-x', `${paddingXScale[this.density]}rem`))
+
+ return result.join('\n')
+ }
+
+ get template() {
+ const vm = this
+ const contents = this.cssVariables.map((method) => vm[`${method}Value`]()).join('\n')
+
+ return `:root {
+${contents}
+ }`
+ }
+
+ updateProperty(e) {
+ const { params } = e
+ const { property } = params
+ this[property] = e.target.value
+ this.updateDOM()
+ }
+
+ updateDOM() {
+ console.log(this.template)
+ document.querySelector('[data-theme-options-target="cssVariablesRoot"]').innerHTML = this.template
+ }
+
+ async save() {
+ console.log('save')
+
+ const response = await put(this.pathValue, {
+ body: {
+ foo: 'bar',
+ },
+ })
+ const data = await response.json()
+ console.log(data)
+ }
+
+ #outputLine(property, value) {
+ return `--${kebabCase(property)}: ${value};`
+ }
+}
diff --git a/app/views/avo/partials/_color_scheme_switcher.html.erb b/app/views/avo/partials/_color_scheme_switcher.html.erb
new file mode 100644
index 0000000000..ef666baa65
--- /dev/null
+++ b/app/views/avo/partials/_color_scheme_switcher.html.erb
@@ -0,0 +1,11 @@
+
+ <%= svg "moon-plus-plus", class: "h-5" %>
+<%#= cookies[:color_scheme] %>
+ <%#= form_with url: color_scheme_path, method: :post do |f| %>
+ <%#= f.hidden_input color_scheme: :light %>
+ <%#= f.button "Light" %>
+ <%# end %>
+ <%= link_to :auto, color_scheme_path(color_scheme: :auto), data: {turbo_method: :post, turbo_frame: :_top} %>
+ <%= link_to :light, color_scheme_path(color_scheme: :light), data: {turbo_method: :post, turbo_frame: :_top} %>
+ <%= link_to :dark, color_scheme_path(color_scheme: :dark), data: {turbo_method: :post, turbo_frame: :_top} %>
+
diff --git a/app/views/avo/partials/_color_theme_override.html.erb b/app/views/avo/partials/_color_theme_override.html.erb
new file mode 100644
index 0000000000..5544a61177
--- /dev/null
+++ b/app/views/avo/partials/_color_theme_override.html.erb
@@ -0,0 +1,8 @@
+
diff --git a/app/views/avo/partials/_css_variables.html.erb b/app/views/avo/partials/_css_variables.html.erb
new file mode 100644
index 0000000000..389efb5faf
--- /dev/null
+++ b/app/views/avo/partials/_css_variables.html.erb
@@ -0,0 +1,9 @@
+
diff --git a/app/views/avo/partials/_navbar.html.erb b/app/views/avo/partials/_navbar.html.erb
index 8066a9fa4a..f3e69da552 100644
--- a/app/views/avo/partials/_navbar.html.erb
+++ b/app/views/avo/partials/_navbar.html.erb
@@ -1,4 +1,4 @@
-<%= content_tag :div, class: class_names("fixed bg-white p-2 w-full flex flex-shrink-0 items-center z-[100] px-4 lg:px-4 border-b space-x-4 lg:space-x-0 h-16", {"print:hidden": Avo.configuration.hide_layout_when_printing}) do %>
+<%= content_tag :div, class: class_names("fixed bg-white dark:bg-[#383838] p-2 w-full flex flex-shrink-0 items-center z-[100] px-4 lg:px-4 border-b space-x-4 lg:space-x-0 h-16", {"print:hidden": Avo.configuration.hide_layout_when_printing}) do %>
<%= a_button class: 'lg:hidden', icon: 'menu', size: :xs, compact: true, style: :text, data: { action: 'click->sidebar#toggleSidebarOnMobile' } %>
@@ -8,8 +8,13 @@
<%= render Avo::Pro::GlobalSearchComponent.new rescue nil %>
-
- <%= render partial: "avo/partials/header" %>
+
+
+ <%= render partial: "avo/partials/header" %>
+
+
+ <%= render partial: "avo/partials/color_scheme_switcher" %>
+
<% end %>
diff --git a/app/views/avo/partials/_theme_options.html.erb b/app/views/avo/partials/_theme_options.html.erb
new file mode 100644
index 0000000000..2016485353
--- /dev/null
+++ b/app/views/avo/partials/_theme_options.html.erb
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= button_tag :save, data: {action: "theme-options#save"} %>
+
+
+
diff --git a/app/views/layouts/avo/application.html.erb b/app/views/layouts/avo/application.html.erb
index 0a492ac6bd..16c29c6648 100644
--- a/app/views/layouts/avo/application.html.erb
+++ b/app/views/layouts/avo/application.html.erb
@@ -21,9 +21,12 @@
<% end %>
<%= render Avo::AssetManager::JavascriptComponent.new asset_manager: Avo.asset_manager %>
<%= render partial: 'avo/partials/head' %>
+ <%= render partial: 'avo/partials/color_theme_override' %>
+ <%= render partial: 'avo/partials/css_variables' %>
-
-
+
+ <%= render partial: 'avo/partials/theme_options' %>
+
<%= render partial: "avo/partials/navbar" %>