satis 1.0.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +92 -0
- data/Rakefile +23 -0
- data/app/assets/config/satis_manifest.js +1 -0
- data/app/assets/stylesheets/satis/application.css +15 -0
- data/app/components/satis/appearance_switcher/component.html.slim +6 -0
- data/app/components/satis/appearance_switcher/component.rb +11 -0
- data/app/components/satis/appearance_switcher/component.scss +34 -0
- data/app/components/satis/appearance_switcher/component_controller.js +62 -0
- data/app/components/satis/application_component.rb +50 -0
- data/app/components/satis/avatar/component.html.slim +7 -0
- data/app/components/satis/avatar/component.rb +52 -0
- data/app/components/satis/breadcrumbs/component.html.slim +8 -0
- data/app/components/satis/breadcrumbs/component.rb +23 -0
- data/app/components/satis/breadcrumbs/component.scss +19 -0
- data/app/components/satis/breadcrumbs/crumb.slim +8 -0
- data/app/components/satis/card/component.html.slim +54 -0
- data/app/components/satis/card/component.md +14 -0
- data/app/components/satis/card/component.rb +41 -0
- data/app/components/satis/card/component.scss +15 -0
- data/app/components/satis/date_time_picker/component.html.slim +48 -0
- data/app/components/satis/date_time_picker/component.md +11 -0
- data/app/components/satis/date_time_picker/component.rb +48 -0
- data/app/components/satis/date_time_picker/component.scss +5 -0
- data/app/components/satis/date_time_picker/component_controller.js +499 -0
- data/app/components/satis/dropdown/component.html.slim +36 -0
- data/app/components/satis/dropdown/component.md +48 -0
- data/app/components/satis/dropdown/component.rb +77 -0
- data/app/components/satis/dropdown/component.scss +10 -0
- data/app/components/satis/dropdown/component_controller.js +547 -0
- data/app/components/satis/flash_messages/component.html.slim +3 -0
- data/app/components/satis/flash_messages/component.rb +31 -0
- data/app/components/satis/flash_messages/component.scss +18 -0
- data/app/components/satis/flash_messages/message.html.slim +8 -0
- data/app/components/satis/info/component.html.slim +4 -0
- data/app/components/satis/info/component.rb +22 -0
- data/app/components/satis/info_item/component.html.slim +7 -0
- data/app/components/satis/info_item/component.rb +19 -0
- data/app/components/satis/input/component.html.slim +11 -0
- data/app/components/satis/input/component.rb +38 -0
- data/app/components/satis/input/component.scss +50 -0
- data/app/components/satis/input/element.html.slim +2 -0
- data/app/components/satis/map/component.html.slim +2 -0
- data/app/components/satis/map/component.rb +17 -0
- data/app/components/satis/map/component.scss +9 -0
- data/app/components/satis/map/component_controller.js +37 -0
- data/app/components/satis/menu/component.html.slim +13 -0
- data/app/components/satis/menu/component.md +1 -0
- data/app/components/satis/menu/component.rb +16 -0
- data/app/components/satis/menu/component_controller.js +62 -0
- data/app/components/satis/menu_item/component.html.slim +16 -0
- data/app/components/satis/menu_item/component.rb +14 -0
- data/app/components/satis/page/component.html.slim +45 -0
- data/app/components/satis/page/component.rb +15 -0
- data/app/components/satis/page/component_controller.js +86 -0
- data/app/components/satis/sidebar_menu/component.html.slim +3 -0
- data/app/components/satis/sidebar_menu/component.rb +17 -0
- data/app/components/satis/sidebar_menu/component.scss +0 -0
- data/app/components/satis/sidebar_menu/component_controller.js +9 -0
- data/app/components/satis/sidebar_menu/mobile/component.html.slim +3 -0
- data/app/components/satis/sidebar_menu/mobile/component.rb +10 -0
- data/app/components/satis/sidebar_menu_item/component.html.slim +15 -0
- data/app/components/satis/sidebar_menu_item/component.rb +20 -0
- data/app/components/satis/sidebar_menu_item/component.scss +27 -0
- data/app/components/satis/sidebar_menu_item/component_controller.js +62 -0
- data/app/components/satis/sidebar_menu_item/mobile/component.html.slim +17 -0
- data/app/components/satis/sidebar_menu_item/mobile/component.rb +10 -0
- data/app/components/satis/switch/component.html.slim +14 -0
- data/app/components/satis/switch/component.rb +24 -0
- data/app/components/satis/switch/component_controller.js +49 -0
- data/app/components/satis/tab/component.rb +35 -0
- data/app/components/satis/tabs/component.html.slim +23 -0
- data/app/components/satis/tabs/component.md +21 -0
- data/app/components/satis/tabs/component.rb +16 -0
- data/app/components/satis/tabs/component.scss +33 -0
- data/app/components/satis/tabs/component_controller.js +123 -0
- data/app/controllers/satis/application_controller.rb +4 -0
- data/app/helpers/satis/application_helper.rb +15 -0
- data/app/jobs/satis/application_job.rb +4 -0
- data/app/mailers/satis/application_mailer.rb +6 -0
- data/app/models/satis/application_record.rb +5 -0
- data/app/views/shared/_fields_for.html.slim +35 -0
- data/config/routes.rb +5 -0
- data/lib/satis/action_controller_helpers.rb +29 -0
- data/lib/satis/configuration.rb +61 -0
- data/lib/satis/engine.rb +27 -0
- data/lib/satis/forms/builder.rb +440 -0
- data/lib/satis/forms/concerns/buttons.rb +49 -0
- data/lib/satis/forms/concerns/file.rb +35 -0
- data/lib/satis/forms/concerns/options.rb +44 -0
- data/lib/satis/forms/concerns/required.rb +68 -0
- data/lib/satis/forms/concerns/select.rb +95 -0
- data/lib/satis/helpers/container.rb +83 -0
- data/lib/satis/menus/builder.rb +13 -0
- data/lib/satis/menus/item.rb +34 -0
- data/lib/satis/menus/menu.rb +23 -0
- data/lib/satis/version.rb +3 -0
- data/lib/satis.rb +36 -0
- data/lib/tasks/satis_tasks.rake +4 -0
- metadata +213 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Satis
|
|
4
|
+
module Info
|
|
5
|
+
class Component < Satis::ApplicationComponent
|
|
6
|
+
renders_many :items, lambda { |*args|
|
|
7
|
+
args.last.merge!(group: group)
|
|
8
|
+
component = Satis::InfoItem::Component.new(*args)
|
|
9
|
+
component.original_view_context = original_view_context
|
|
10
|
+
component
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
attr_reader :group, :options
|
|
14
|
+
|
|
15
|
+
def initialize(group: :main, **options)
|
|
16
|
+
super
|
|
17
|
+
@group = group
|
|
18
|
+
@options = options
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
- if content.present? || options[:content].present? || options[:show_always]
|
|
2
|
+
div class=options[:class]
|
|
3
|
+
dt.text-xs.uppercase.text-gray-400 = ct(".#{name}", default: name.to_s.humanize, scope: group)
|
|
4
|
+
dd.text-sm.text-gray-900.dark:text-gray-300
|
|
5
|
+
- if options[:icon]
|
|
6
|
+
i.mr-1 class=options[:icon]
|
|
7
|
+
= content || options[:content] || @placeholder
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Satis
|
|
4
|
+
module InfoItem
|
|
5
|
+
class Component < Satis::ApplicationComponent
|
|
6
|
+
attr_reader :options, :name, :icon, :group
|
|
7
|
+
|
|
8
|
+
def initialize(name, *args, &block)
|
|
9
|
+
super
|
|
10
|
+
@name = name
|
|
11
|
+
@args = args
|
|
12
|
+
@options = args.extract_options!
|
|
13
|
+
@group = options[:group]
|
|
14
|
+
@icon = options[:icon]
|
|
15
|
+
@placeholder = options[:placeholder] || '—'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
.sts-input
|
|
2
|
+
= form.custom_label(attribute, options[:label]) unless options[:label] == false
|
|
3
|
+
.sts-input__input-container class=input_container_class
|
|
4
|
+
- prefixes.each do |prefix|
|
|
5
|
+
= prefix
|
|
6
|
+
= input || form.string_field(attribute, { class: input_class }.merge(options.fetch(:input_html, {})))
|
|
7
|
+
- postfixes.each do |postfix|
|
|
8
|
+
= postfix
|
|
9
|
+
- if hint
|
|
10
|
+
small.form-text.text-muted= hint
|
|
11
|
+
= form.error_text(attribute) if form.has_error?(attribute)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Satis
|
|
4
|
+
module Input
|
|
5
|
+
class Element < ViewComponent::Base
|
|
6
|
+
attr_reader :classes
|
|
7
|
+
|
|
8
|
+
def initialize(classes: nil, colored: true)
|
|
9
|
+
@classes = classes || ''
|
|
10
|
+
@classes += ' colored' if colored
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Component < Satis::ApplicationComponent
|
|
15
|
+
attr_reader :form, :attribute, :options
|
|
16
|
+
|
|
17
|
+
renders_one :label
|
|
18
|
+
renders_one :input
|
|
19
|
+
renders_one :hint
|
|
20
|
+
renders_many :prefixes, Element
|
|
21
|
+
renders_many :postfixes, Element
|
|
22
|
+
|
|
23
|
+
def initialize(form: nil, attribute: nil, **options)
|
|
24
|
+
@form = form
|
|
25
|
+
@attribute = attribute
|
|
26
|
+
@options = options
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def input_class
|
|
30
|
+
[@options.fetch(:input_html, {}).fetch(:class, ''), 'sts-input__input', form.has_error?(attribute) && 'is-invalid'].join(' ')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def input_container_class
|
|
34
|
+
form.has_error?(attribute) && 'is-invalid'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
.sts-input {
|
|
2
|
+
&__input-container {
|
|
3
|
+
@apply mt-1 flex border rounded h-12 text-gray-800 dark:text-gray-300 border-gray-300 dark:bg-gray-800 dark:border-gray-700 overflow-hidden;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
&__label {
|
|
7
|
+
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
&__input-container {
|
|
11
|
+
&.is-invalid {
|
|
12
|
+
@apply border-red-300 text-red-900 placeholder-red-300 focus:outline-none focus:ring-red-500 focus:border-red-500;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&__input {
|
|
17
|
+
@apply flex-1 min-w-0 block w-full border-none py-2 px-3;
|
|
18
|
+
background: transparent;
|
|
19
|
+
|
|
20
|
+
&:focus {
|
|
21
|
+
box-shadow: none;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&__element {
|
|
26
|
+
@apply inline-flex items-center py-2 px-3;
|
|
27
|
+
|
|
28
|
+
&.colored {
|
|
29
|
+
background: rgba(1, 1, 1, 0.1);
|
|
30
|
+
|
|
31
|
+
.dark & {
|
|
32
|
+
background: rgba(255, 255, 255, 0.1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
input {
|
|
37
|
+
@apply -ml-3 -mr-3 -mt-2 -mb-2 w-full;
|
|
38
|
+
|
|
39
|
+
&:focus {
|
|
40
|
+
box-shadow: none;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
* {
|
|
45
|
+
@apply border-none;
|
|
46
|
+
|
|
47
|
+
background-color: transparent;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
.satis-map data-controller="satis-map" data-satis-map-urls-value=@urls data-satis-map-longitude-value=@longitude data-satis-map-latitude-value=@latitude data-satis-map-zoom-level-value=@zoom_level data-satis-map-geo-json-url-value=@geo_json_url
|
|
2
|
+
.sts-map__map data-satis-map-target="container"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Satis
|
|
4
|
+
module Map
|
|
5
|
+
class Component < Satis::ApplicationComponent
|
|
6
|
+
attr_reader :latitude, :longitude, :zoom_level, :geo_json_url
|
|
7
|
+
|
|
8
|
+
def initialize(latitude: 52.09083, longitude: 5.12222, zoom_level: 7, geo_json_url: nil)
|
|
9
|
+
super
|
|
10
|
+
@latitude = latitude
|
|
11
|
+
@longitude = longitude
|
|
12
|
+
@zoom_level = zoom_level
|
|
13
|
+
@geo_json_url = geo_json_url
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// map_controller.js
|
|
2
|
+
import ApplicationController from "../../../../frontend/controllers/application_controller"
|
|
3
|
+
import L from "leaflet"
|
|
4
|
+
|
|
5
|
+
export default class extends ApplicationController {
|
|
6
|
+
static targets = ["container"]
|
|
7
|
+
static values = { urls: String, latitude: Number, longitude: Number, zoomLevel: Number, geoJsonUrl: String }
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
super.connect()
|
|
11
|
+
|
|
12
|
+
// Example https://leafletjs.com/examples/choropleth/
|
|
13
|
+
// Data https://public.opendatasoft.com/explore/?sort=modified&q=netherlands
|
|
14
|
+
|
|
15
|
+
this.map = L.map(this.containerTarget).setView([this.latitudeValue, this.longitudeValue], this.zoomLevelValue)
|
|
16
|
+
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }).addTo(this.map)
|
|
17
|
+
|
|
18
|
+
// this.map.setView() etc... as normal.
|
|
19
|
+
|
|
20
|
+
// Load layers and setup event handlers, for example:
|
|
21
|
+
fetch(this.geoJsonUrlValue)
|
|
22
|
+
.then((response) => response.json())
|
|
23
|
+
.then((data) => {
|
|
24
|
+
L.geoJSON(data, {
|
|
25
|
+
onEachFeature: (feature, layer) => {
|
|
26
|
+
layer.on("click", () => this.onClick(layer))
|
|
27
|
+
},
|
|
28
|
+
}).addTo(this.map)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
disconnect() {
|
|
33
|
+
this.map.remove()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onClick(layer) {}
|
|
37
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
div data-controller="satis-menu" data-action="mouseover->satis-menu#show mouseleave->satis-menu#hide"
|
|
2
|
+
- if content
|
|
3
|
+
= content
|
|
4
|
+
- else
|
|
5
|
+
button.inline-flex.items-center.justify-center.h-8.w-8.rounded-full.focus:outline-none.focus:ring-2.focus:ring-offset-2.focus:ring-primary-500.dark:text-gray-500 aria-expanded="false" aria-haspopup="true" type="button"
|
|
6
|
+
span.font-semibold.flex-1 class="#{menu.items.present? ? '' : 'text-gray-200'}"
|
|
7
|
+
i class=icon
|
|
8
|
+
|
|
9
|
+
- if menu.items.present?
|
|
10
|
+
ul.hidden.z-10.bg-white.dark:bg-gray-400.border.dark:border-gray-800.rounded-md.shadow-lg.min-w-max data-satis-menu-target="submenu" data-satis-menu-submenu-placement="bottom"
|
|
11
|
+
- menu.items.each do |item|
|
|
12
|
+
= render(Satis::MenuItem::Component.new(item: item))
|
|
13
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
https://tailwindcomponents.com/component/nestable-dropdown-menu
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Satis
|
|
4
|
+
module Menu
|
|
5
|
+
class Component < Satis::ApplicationComponent
|
|
6
|
+
# renders_many :tabs, Tab::Component
|
|
7
|
+
attr_reader :menu, :icon
|
|
8
|
+
|
|
9
|
+
def initialize(menu, icon: nil)
|
|
10
|
+
super
|
|
11
|
+
@menu = menu
|
|
12
|
+
@icon = icon || 'fa-solid fa-ellipsis-vertical'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import ApplicationController from "../../../../frontend/controllers/application_controller"
|
|
2
|
+
// FIXME: Is this full path really needed?
|
|
3
|
+
import { debounce } from "../../../../frontend/utils"
|
|
4
|
+
import { createPopper } from "@popperjs/core"
|
|
5
|
+
|
|
6
|
+
export default class extends ApplicationController {
|
|
7
|
+
static targets = ["submenu", "toggle"]
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
super.connect()
|
|
11
|
+
|
|
12
|
+
if (this.hasSubmenuTarget) {
|
|
13
|
+
this.popperInstance = createPopper(this.element, this.submenuTarget, {
|
|
14
|
+
offset: [-20, 2],
|
|
15
|
+
placement: this.submenuTarget.getAttribute("data-satis-menu-submenu-placement") || "auto",
|
|
16
|
+
modifiers: [
|
|
17
|
+
{
|
|
18
|
+
name: "flip",
|
|
19
|
+
enabled: true,
|
|
20
|
+
options: {
|
|
21
|
+
boundary: this.element.closest(".satis-card"),
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "preventOverflow",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
show(event) {
|
|
33
|
+
if (this.hasSubmenuTarget && (!this.hasToggleTarget || (this.hasToggleTarget && this.toggledOn))) {
|
|
34
|
+
this.submenuTarget.classList.remove("hidden")
|
|
35
|
+
this.submenuTarget.setAttribute("data-show", "")
|
|
36
|
+
this.popperInstance.update()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
hide(event) {
|
|
41
|
+
if (this.hasSubmenuTarget) {
|
|
42
|
+
this.submenuTarget.classList.add("hidden")
|
|
43
|
+
this.submenuTarget.removeAttribute("data-show")
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
toggle(event) {
|
|
48
|
+
if (this.hasToggleTarget) {
|
|
49
|
+
this.toggleTarget.classList.toggle("hidden")
|
|
50
|
+
this.triggerEvent(this.toggleTarget, "toggle", { toggled: !this.toggleTarget.classList.contains("hidden"), id: this.toggleTarget.getAttribute("id") })
|
|
51
|
+
if (this.toggleTarget.classList.contains("hidden")) {
|
|
52
|
+
this.hide(event)
|
|
53
|
+
} else {
|
|
54
|
+
this.show(event)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get toggledOn() {
|
|
60
|
+
return !this.toggleTarget.classList.contains("hidden")
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
li.rounded-sm.px-3.py-1.hover:bg-gray-100.dark:hover:bg-gray-200.flex.items-center data-controller="satis-menu" data-action="mouseover->satis-menu#show mouseleave->satis-menu#hide"
|
|
2
|
+
a.cursor-pointer.py-1.w-full.text-left.flex.items-center.outline-none.focus:outline-none href=item.link *item.link_attributes
|
|
3
|
+
span.pr-1.flex-shrink-0.w-6
|
|
4
|
+
- if item.icon.present?
|
|
5
|
+
i class=item.icon
|
|
6
|
+
span.pr-1.flex-1 = item.label
|
|
7
|
+
span.pr-1.flex-shrink-0.w-6
|
|
8
|
+
- if item.type == :toggle
|
|
9
|
+
i.fal.fa-check.hidden data-satis-menu-target="toggle" id="#{item.id}"
|
|
10
|
+
|
|
11
|
+
- if item.menu
|
|
12
|
+
ul.hidden.bg-white.border.rounded-md.shadow-lg data-satis-menu-target="submenu" data-satis-menu-submenu-placement="left"
|
|
13
|
+
- item.menu.items.each do |sub_item|
|
|
14
|
+
= render(Satis::MenuItem::Component.new(item: sub_item))
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
doctype html
|
|
2
|
+
html lang="en"
|
|
3
|
+
head
|
|
4
|
+
= head
|
|
5
|
+
body
|
|
6
|
+
.h-screen.flex.overflow-hidden.bg-gray-100.dark:bg-gray-600 data-controller="satis-page"
|
|
7
|
+
/ Off-canvas menu for mobile, show/hide based on off-canvas menu state.
|
|
8
|
+
.fixed.inset-0.flex.z-40.md:hidden.hidden aria-modal="true" role="dialog" data-satis-page-target="dialog"
|
|
9
|
+
|
|
10
|
+
.fixed.inset-0.bg-gray-600.bg-opacity-75.hidden aria-hidden="true" data-satis-page-target="overlay"
|
|
11
|
+
|
|
12
|
+
.relative.flex-1.flex.flex-col.max-w-xs.w-full.pt-5.pb-4.bg-white.transform.hidden data-satis-page-target="offCanvasMenu"
|
|
13
|
+
|
|
14
|
+
.absolute.top-0.right-0.-mr-12.pt-2.hidden data-satis-page-target="closeButton"
|
|
15
|
+
button.ml-1.flex.items-center.justify-center.h-10.w-10.rounded-full.focus:outline-none.focus:ring-2.focus:ring-inset.focus:ring-white data-action="satis-page#close"
|
|
16
|
+
span.sr-only Close sidebar
|
|
17
|
+
i.fal.fa-2x.fa-xmark.text-white aria-hidden="true"
|
|
18
|
+
= sidebar_mobile
|
|
19
|
+
|
|
20
|
+
.flex-shrink-0.w-14 aria-hidden="true"
|
|
21
|
+
/ Dummy element to force sidebar to shrink to fit close icon
|
|
22
|
+
|
|
23
|
+
/ SIDEBAR - for desktop
|
|
24
|
+
.hidden.md:flex.md:flex-shrink-0
|
|
25
|
+
.flex.flex-col.w-64
|
|
26
|
+
/! Sidebar component, swap this element with another sidebar if you like
|
|
27
|
+
|
|
28
|
+
.flex.flex-col.flex-grow.border-r.border-gray-200.pt-5.pb-4.bg-white.overflow-y-auto.dark:bg-gray-900.dark:border-gray-700
|
|
29
|
+
= sidebar
|
|
30
|
+
|
|
31
|
+
/ TOPBAR
|
|
32
|
+
.flex.flex-col.w-0.flex-1.overflow-hidden
|
|
33
|
+
.relative.z-10.flex-shrink-0.flex.h-16.bg-white.shadow.dark:bg-gray-900.dark:border-gray-700
|
|
34
|
+
button.px-4.border-r.border-gray-200.text-gray-500.focus:outline-none.focus:ring-2.focus:ring-inset.focus:ring-primary-500.md:hidden data-action="satis-page#open"
|
|
35
|
+
span.sr-only Open sidebar
|
|
36
|
+
i.fal.fa-2x.fa-bars
|
|
37
|
+
|
|
38
|
+
.flex-1.px-4.flex.justify-between
|
|
39
|
+
= navbar
|
|
40
|
+
|
|
41
|
+
main.flex-1.relative.overflow-y-auto.focus:outline-none
|
|
42
|
+
.py-4
|
|
43
|
+
.max-w.mx-auto.px-4.sm:px-4.md:px-4
|
|
44
|
+
= body
|
|
45
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Satis
|
|
4
|
+
module Page
|
|
5
|
+
class Component < Satis::ApplicationComponent
|
|
6
|
+
renders_one :head
|
|
7
|
+
renders_one :navbar
|
|
8
|
+
renders_one :sidebar_mobile
|
|
9
|
+
renders_one :sidebar
|
|
10
|
+
renders_one :body
|
|
11
|
+
|
|
12
|
+
def initialize(**options); end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import ApplicationController from "../../../../frontend/controllers/application_controller"
|
|
2
|
+
|
|
3
|
+
export default class extends ApplicationController {
|
|
4
|
+
static targets = ["closeButton", "openButton", "offCanvasMenu", "overlay", "dialog"]
|
|
5
|
+
connect() {
|
|
6
|
+
super.connect()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
close(event) {
|
|
10
|
+
// | Entering: "transition-opacity ease-linear duration-300"
|
|
11
|
+
// | From: "opacity-0"
|
|
12
|
+
// | To: "opacity-100"
|
|
13
|
+
// | Leaving: "transition-opacity ease-linear duration-300"
|
|
14
|
+
// | From: "opacity-100"
|
|
15
|
+
// | To: "opacity-0"
|
|
16
|
+
// overlay
|
|
17
|
+
this.overlayTarget.classList.add("transition-opacity", "ease-linear", "duration-300", "opacity-0")
|
|
18
|
+
|
|
19
|
+
// | Entering: "transition ease-in-out duration-300 transform"
|
|
20
|
+
// | From: "-translate-x-full"
|
|
21
|
+
// | To: "translate-x-0"
|
|
22
|
+
// | Leaving: "transition ease-in-out duration-300 transform"
|
|
23
|
+
// | From: "translate-x-0"
|
|
24
|
+
// | To: "-translate-x-full"
|
|
25
|
+
// offCanvasMenu
|
|
26
|
+
this.offCanvasMenuTarget.classList.add("transition", "ease-in-out", "duration-1000")
|
|
27
|
+
|
|
28
|
+
// | Entering: "ease-in-out duration-300"
|
|
29
|
+
// | From: "opacity-0"
|
|
30
|
+
// | To: "opacity-100"
|
|
31
|
+
// | Leaving: "ease-in-out duration-300"
|
|
32
|
+
// | From: "opacity-100"
|
|
33
|
+
// | To: "opacity-0"
|
|
34
|
+
// closeButton
|
|
35
|
+
this.closeButtonTarget.classList.add("ease-in-out", "duration-300", "opacity-0")
|
|
36
|
+
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
this.dialogTarget.classList.add("hidden")
|
|
39
|
+
this.overlayTarget.classList.add("hidden")
|
|
40
|
+
this.offCanvasMenuTarget.classList.add("hidden")
|
|
41
|
+
this.closeButtonTarget.classList.add("hidden")
|
|
42
|
+
|
|
43
|
+
this.overlayTarget.classList.remove("transition-opacity", "ease-linear", "duration-300")
|
|
44
|
+
this.offCanvasMenuTarget.classList.remove("transition", "ease-in-out", "duration-300")
|
|
45
|
+
this.closeButtonTarget.classList.remove("ease-in-out", "duration-300")
|
|
46
|
+
}, 100)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
open(event) {
|
|
50
|
+
this.dialogTarget.classList.remove("hidden")
|
|
51
|
+
this.overlayTarget.classList.remove("hidden")
|
|
52
|
+
this.offCanvasMenuTarget.classList.remove("hidden")
|
|
53
|
+
this.closeButtonTarget.classList.remove("hidden")
|
|
54
|
+
|
|
55
|
+
// | Entering: "transition-opacity ease-linear duration-300"
|
|
56
|
+
// | From: "opacity-0"
|
|
57
|
+
// | To: "opacity-100"
|
|
58
|
+
// | Leaving: "transition-opacity ease-linear duration-300"
|
|
59
|
+
// | From: "opacity-100"
|
|
60
|
+
// | To: "opacity-0"
|
|
61
|
+
// overlay
|
|
62
|
+
this.overlayTarget.classList.remove("opacity-0")
|
|
63
|
+
this.overlayTarget.classList.add("transition-opacity", "ease-linear", "duration-300", "opacity-100")
|
|
64
|
+
|
|
65
|
+
// | Entering: "transition ease-in-out duration-300 transform"
|
|
66
|
+
// | From: "-translate-x-full"
|
|
67
|
+
// | To: "translate-x-0"
|
|
68
|
+
// | Leaving: "transition ease-in-out duration-300 transform"
|
|
69
|
+
// | From: "translate-x-0"
|
|
70
|
+
// | To: "-translate-x-full"
|
|
71
|
+
// offCanvasMenu
|
|
72
|
+
this.offCanvasMenuTarget.classList.add("transition", "ease-in-out", "duration-1000")
|
|
73
|
+
this.offCanvasMenuTarget.classList.remove("-translate-x-full")
|
|
74
|
+
this.offCanvasMenuTarget.classList.add("transition", "ease-in-out", "duration-1000", "transform", "translate-x-0")
|
|
75
|
+
|
|
76
|
+
// | Entering: "ease-in-out duration-300"
|
|
77
|
+
// | From: "opacity-0"
|
|
78
|
+
// | To: "opacity-100"
|
|
79
|
+
// | Leaving: "ease-in-out duration-300"
|
|
80
|
+
// | From: "opacity-100"
|
|
81
|
+
// | To: "opacity-0"
|
|
82
|
+
// closeButton
|
|
83
|
+
this.closeButtonTarget.classList.remove("opacity-0")
|
|
84
|
+
this.closeButtonTarget.classList.add("ease-in-out", "duration-300", "opacity-100")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Satis
|
|
4
|
+
module SidebarMenu
|
|
5
|
+
class Component < Satis::ApplicationComponent
|
|
6
|
+
attr_reader :menu, :menu_options
|
|
7
|
+
|
|
8
|
+
renders_many :items
|
|
9
|
+
|
|
10
|
+
def initialize(menu, **options)
|
|
11
|
+
super
|
|
12
|
+
@menu = menu
|
|
13
|
+
@menu_options = options
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import ApplicationController from "../../../../frontend/controllers/application_controller"
|
|
2
|
+
// FIXME: Is this full path really needed?
|
|
3
|
+
import { debounce } from "../../../../frontend/utils"
|
|
4
|
+
|
|
5
|
+
export default class extends ApplicationController {
|
|
6
|
+
connect() {
|
|
7
|
+
super.connect()
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.sts-sidebar-menu-item class="#{item.level >= 1 ? 'pl-4' : ''}" data-controller="satis-sidebar-menu-item"
|
|
2
|
+
a.sts-sidebar-menu-item__link aria-controls="sub-menu-1" aria-expanded="false" href=item.link *item.link_attributes data-satis-sidebar-menu-item-target="link" data-action=data_actions
|
|
3
|
+
- if item.icon
|
|
4
|
+
i.sts-sidebar-menu-item__icon.fa-lg class=item.icon style="width: 20px;"
|
|
5
|
+
- else
|
|
6
|
+
i.sts-sidebar-menu-item__no-icon style="width: 20px;"
|
|
7
|
+
|
|
8
|
+
span.sts-sidebar-menu-item__label= item.label
|
|
9
|
+
- if item.menu
|
|
10
|
+
i.sts-sidebar-menu-item__menu-icon.fa-solid.fa-angle-right style="width: 20px;" data-satis-sidebar-menu-item-target="indicator"
|
|
11
|
+
|
|
12
|
+
- if item.menu
|
|
13
|
+
div.hidden data-satis-sidebar-menu-item-target="submenu"
|
|
14
|
+
- item.menu.items.each do |sub_item|
|
|
15
|
+
= render(Satis::SidebarMenuItem::Component.new(item: sub_item))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Satis
|
|
4
|
+
module SidebarMenuItem
|
|
5
|
+
class Component < Satis::ApplicationComponent
|
|
6
|
+
attr_reader :item, :menu_options
|
|
7
|
+
|
|
8
|
+
# renders_many :items
|
|
9
|
+
def initialize(**options)
|
|
10
|
+
@item = options[:item]
|
|
11
|
+
@menu_options = options.fetch(:menu_options, {})
|
|
12
|
+
@actions = item.link_attributes.delete(:'data-action')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def data_actions
|
|
16
|
+
"click->satis-sidebar-menu-item#open #{@actions}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.sts-sidebar-menu-item {
|
|
2
|
+
@apply pt-1;
|
|
3
|
+
|
|
4
|
+
& a.focus {
|
|
5
|
+
background: rgba(1, 1, 1, 0.1);
|
|
6
|
+
|
|
7
|
+
.dark & {
|
|
8
|
+
background: rgba(255, 255, 255, 0.3);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&__link {
|
|
13
|
+
@apply text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-900 w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500
|
|
14
|
+
}
|
|
15
|
+
&__icon {
|
|
16
|
+
@apply mr-3 flex-shrink-0 h-6 w-6 text-gray-400 group-hover:text-gray-500;
|
|
17
|
+
}
|
|
18
|
+
&__no-icon {
|
|
19
|
+
@apply mr-3 flex-shrink-0 h-6;
|
|
20
|
+
}
|
|
21
|
+
&__label {
|
|
22
|
+
@apply flex-1;
|
|
23
|
+
}
|
|
24
|
+
&__menu-icon {
|
|
25
|
+
@apply text-gray-300 ml-3 flex-shrink-0 h-5 w-5 transform group-hover:text-gray-400 transition-colors ease-in-out duration-150;
|
|
26
|
+
}
|
|
27
|
+
}
|