satis 1.0.66
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|