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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3dffc5ab90aa3d26440c35d5ca251870e5f5c6863706108380fd117629133f2e
|
4
|
+
data.tar.gz: 72a53d8079ca58762c1e7de4b5a91f8d7be3898fbf79d1f2f4749772ea0d9aab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6bc1e23b0a4197f01e9b976ab27e63e07da645db958b0e466a87efe4a10bb4c830a8f5b4d18a573959a046039a17a7544aec76f1a31856e4247b0b649066a45c
|
7
|
+
data.tar.gz: 9532fbe01e4f4b2a8ba8222276a9afdd87209c0ac660a7f3b2a5efae80a43db1c17978dc4b615a83a74c300f7f1096732e72d6ed610def3753b37db828caa699
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2021 Tom de Grunt
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Satis
|
2
|
+
|
3
|
+
Tailwind CSS based UI framework for Rails.
|
4
|
+
We use:
|
5
|
+
|
6
|
+
- [TailwindCSS](https://tailwindcss.com)
|
7
|
+
- [TailwindUI](https://tailwindui.com)
|
8
|
+
- [FontAwesome 6](https://fontawesome.com/v6.0/)
|
9
|
+
- [ViewComponent](https://viewcomponent.org)
|
10
|
+
- [HotWired](https://hotwired.dev)
|
11
|
+
- [BEM](https://cssguidelin.es/#bem-like-naming)
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
You can use satis helpers in your own helpers:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
def mycard(&block)
|
19
|
+
sts.card(icon: 'fad fa-user', title: "Profile", &block)
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
and then in your template:
|
24
|
+
|
25
|
+
```slim
|
26
|
+
= mycard do |card|
|
27
|
+
```
|
28
|
+
|
29
|
+
### Components
|
30
|
+
|
31
|
+
Each component has it's own documentation in the component folder.
|
32
|
+
Other engines can add components to Satis too:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
Satis.add_helper :name, ViewComponent::Class
|
36
|
+
```
|
37
|
+
|
38
|
+
### Forms
|
39
|
+
|
40
|
+
```slim
|
41
|
+
= sts.form_with model: @user, url: profile_url, class: 'mt-2' do |f|
|
42
|
+
= f.input :id, as: :hidden
|
43
|
+
= f.input :first_name
|
44
|
+
= f.input :last_name
|
45
|
+
= f.association :account, collection: policy_scope(Account).with(@user.account_id), as: :dropdown
|
46
|
+
= f.input :location_id, url: select_locations_url(format: :html), as: :dropdown, hint: "The user's main location"
|
47
|
+
|
48
|
+
= f.button
|
49
|
+
= f.submit
|
50
|
+
= f.reset
|
51
|
+
= f.continue
|
52
|
+
```
|
53
|
+
|
54
|
+
### Browser detection
|
55
|
+
|
56
|
+
Satis now includes browser detection using the browser gem, you can use it in controllers and in your views:
|
57
|
+
|
58
|
+
```
|
59
|
+
sts.browser.chrome?
|
60
|
+
sts.browser.mobile?
|
61
|
+
```
|
62
|
+
|
63
|
+
For more information see the [browser gem](https://github.com/fnando/browser)
|
64
|
+
|
65
|
+
## Dark
|
66
|
+
|
67
|
+
bg-gray-800 - hoofd achtergrond card/sidebar
|
68
|
+
bg-gray-700 - highlight card/sidebar / hover
|
69
|
+
text-gray-300 - tekst kleur
|
70
|
+
bg-gray-600 - body achtergrond kleur
|
71
|
+
|
72
|
+
## Known issues
|
73
|
+
|
74
|
+
- dropdown results will not overlap the card, they should, just like date-time picker
|
75
|
+
- dropdown triggers on-change upon initial population (for attributes), which is different from select's
|
76
|
+
- dropdown hoogte van results is niet altijd goed
|
77
|
+
- table state is not saved
|
78
|
+
- table columns removing is weird, you really need to be on the left part of the screen to drag
|
79
|
+
- table filters initially passed should not be editable
|
80
|
+
- sidebar has no small / collapsed version
|
81
|
+
|
82
|
+
## Installation
|
83
|
+
|
84
|
+
Add this line to your application's Gemfile:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
gem 'satis'
|
88
|
+
```
|
89
|
+
|
90
|
+
## License
|
91
|
+
|
92
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
4
|
+
load "rails/tasks/engine.rake"
|
5
|
+
|
6
|
+
load "rails/tasks/statistics.rake"
|
7
|
+
|
8
|
+
require "bundler/gem_tasks"
|
9
|
+
|
10
|
+
require "rake/testtask"
|
11
|
+
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
13
|
+
t.libs << "test"
|
14
|
+
t.pattern = "test/**/*_test.rb"
|
15
|
+
t.verbose = false
|
16
|
+
t.warning = false
|
17
|
+
end
|
18
|
+
|
19
|
+
task default: :test
|
20
|
+
|
21
|
+
# Adds the Auxilium semver task
|
22
|
+
spec = Gem::Specification.find_by_name "auxilium"
|
23
|
+
load "#{spec.gem_dir}/lib/tasks/semver.rake"
|
@@ -0,0 +1 @@
|
|
1
|
+
//= link_directory ../stylesheets/satis .css
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,6 @@
|
|
1
|
+
.sts-appearance-switcher data-controller="satis-appearance-switcher" data-action='click->satis-appearance-switcher#switch'
|
2
|
+
.sts-appearance-switcher__theme.sts-appearance-switcher__theme_light data-satis-appearance-switcher-target="light"
|
3
|
+
i.fa-solid.fa-sun.sts-appearance-switcher__icon.sts-appearance-switcher__icon_light
|
4
|
+
.sts-appearance-switcher__theme.sts-appearance-switcher__theme_dark data-satis-appearance-switcher-target="dark"
|
5
|
+
i.fa-solid.fa-moon-stars.sts-appearance-switcher__icon.sts-appearance-switcher__icon_dark
|
6
|
+
.sts-appearance-switcher__label Switch theme
|
@@ -0,0 +1,34 @@
|
|
1
|
+
.sts-appearance-switcher {
|
2
|
+
@apply w-8 h-8 bg-white rounded-full text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 dark:bg-gray-900 dark:border-gray-700 overflow-hidden relative;
|
3
|
+
text-align: center;
|
4
|
+
|
5
|
+
&__theme {
|
6
|
+
@apply absolute;
|
7
|
+
|
8
|
+
&_light {
|
9
|
+
top: 5px;
|
10
|
+
left: 7px;
|
11
|
+
}
|
12
|
+
|
13
|
+
&_dark {
|
14
|
+
top: 4px;
|
15
|
+
left: 8px;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
&__icon {
|
20
|
+
font-size: larger;
|
21
|
+
|
22
|
+
&_light {
|
23
|
+
@apply text-yellow-500;
|
24
|
+
}
|
25
|
+
|
26
|
+
&_dark {
|
27
|
+
@apply text-indigo-600;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
&__label {
|
32
|
+
@apply sr-only;
|
33
|
+
}
|
34
|
+
}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
import ApplicationController from "../../../../frontend/controllers/application_controller"
|
2
|
+
import { getInitialTheme } from "../../../../frontend/utils"
|
3
|
+
|
4
|
+
/*
|
5
|
+
* Theme controller
|
6
|
+
*
|
7
|
+
* div data-controller="satis-appearance-switcher" data-action='click->satis-appearance-switcher#switch'
|
8
|
+
* i.fal.fa-sun data-satis-appearance-switcher-target="light"
|
9
|
+
* i.fal.fa-moon-stars data-satis-appearance-switcher-target="dark"
|
10
|
+
*
|
11
|
+
*/
|
12
|
+
export default class extends ApplicationController {
|
13
|
+
static targets = ["light", "dark"]
|
14
|
+
|
15
|
+
connect() {
|
16
|
+
super.connect()
|
17
|
+
|
18
|
+
const theme = getInitialTheme()
|
19
|
+
this.rawSetTheme(theme, false)
|
20
|
+
}
|
21
|
+
|
22
|
+
switch() {
|
23
|
+
const theme = getInitialTheme()
|
24
|
+
|
25
|
+
if (theme == "dark") {
|
26
|
+
this.rawSetTheme("light", true)
|
27
|
+
} else {
|
28
|
+
this.rawSetTheme("dark", true)
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
rawSetTheme(rawTheme, delay) {
|
33
|
+
const root = window.document.documentElement
|
34
|
+
const eventLight = new CustomEvent('theme-change', { detail: { theme: 'light' } });
|
35
|
+
const eventDark = new CustomEvent('theme-change', { detail: { theme: 'dark' } });
|
36
|
+
const isDark = rawTheme === "dark"
|
37
|
+
|
38
|
+
if (delay == true) {
|
39
|
+
this.lightTarget.classList.add("transition", "ease-in-out", "duration-1000")
|
40
|
+
this.darkTarget.classList.add("transition", "ease-in-out", "duration-1000")
|
41
|
+
}
|
42
|
+
|
43
|
+
if (isDark) {
|
44
|
+
window.dispatchEvent(eventDark);
|
45
|
+
this.lightTarget.classList.add("transform", "translate-y-7")
|
46
|
+
this.darkTarget.classList.remove("transform", "-translate-y-7")
|
47
|
+
} else {
|
48
|
+
window.dispatchEvent(eventLight);
|
49
|
+
this.lightTarget.classList.remove("transform", "translate-y-7")
|
50
|
+
this.darkTarget.classList.add("transform", "-translate-y-7")
|
51
|
+
}
|
52
|
+
|
53
|
+
localStorage.setItem("color-theme", rawTheme)
|
54
|
+
setTimeout(
|
55
|
+
() => {
|
56
|
+
root.classList.remove(isDark ? "light" : "dark")
|
57
|
+
root.classList.add(rawTheme)
|
58
|
+
},
|
59
|
+
delay ? 500 : 0
|
60
|
+
)
|
61
|
+
}
|
62
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Satis
|
4
|
+
class ApplicationComponent < ViewComponent::Base
|
5
|
+
include ViewComponent::SlotableV2
|
6
|
+
include ActionView::Helpers::TranslationHelper
|
7
|
+
|
8
|
+
attr_accessor :original_view_context
|
9
|
+
|
10
|
+
#
|
11
|
+
# This provides us with a translation helper which scopes into the original view
|
12
|
+
# and thereby conveniently scopes the translations.
|
13
|
+
#
|
14
|
+
# In your component.html.slim you can use:
|
15
|
+
# ```slim
|
16
|
+
# = ct(".#{tab.name}", scope: [group.to_sym])
|
17
|
+
# ````
|
18
|
+
#
|
19
|
+
# It'll then try and find a translation with scope: en.admin.spaces.edit.tabs.main.admin_versions
|
20
|
+
#
|
21
|
+
def ct(key = nil, **options)
|
22
|
+
scope = Array.wrap(options.delete(:scope))
|
23
|
+
|
24
|
+
scope = if scope
|
25
|
+
scope.unshift(i18n_scope)
|
26
|
+
else
|
27
|
+
[i18n_scope]
|
28
|
+
end
|
29
|
+
|
30
|
+
scope = original_i18n_scope.concat(scope)
|
31
|
+
|
32
|
+
key = key&.to_s unless key.is_a?(String)
|
33
|
+
key = "#{scope.join('.')}#{key}" if key.start_with?('.')
|
34
|
+
|
35
|
+
original_view_context.t(key, **options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def original_virtual_path
|
39
|
+
original_view_context.instance_variable_get(:@virtual_path)
|
40
|
+
end
|
41
|
+
|
42
|
+
def original_i18n_scope
|
43
|
+
original_virtual_path.sub(%r{^/}, '').gsub(%r{/_?}, '.').split('.')
|
44
|
+
end
|
45
|
+
|
46
|
+
def i18n_scope
|
47
|
+
self.class.name.split('::').second.underscore.to_sym
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
button.inline-flex.items-center.justify-center.rounded-full.bg-gray-500.focus:outline-none.focus:ring-2.focus:ring-offset-2.focus:ring-primary-500 aria-expanded="false" aria-haspopup="true" type="button" class=options[:class]
|
2
|
+
- if photo&.attached?
|
3
|
+
img class="rounded-full" src=photo_url alt=name class=options[:class]
|
4
|
+
- elsif gravatar?
|
5
|
+
img class="rounded-full" src=gravatar_url alt=name class=options[:class]
|
6
|
+
- else
|
7
|
+
span.text-sm.font-medium.leading-none.text-white = initials
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Satis
|
4
|
+
module Avatar
|
5
|
+
class Component < Satis::ApplicationComponent
|
6
|
+
attr_reader :name, :photo, :email, :options
|
7
|
+
|
8
|
+
def initialize(name: nil, email: nil, photo: nil, **options)
|
9
|
+
super
|
10
|
+
@name = name
|
11
|
+
@photo = photo
|
12
|
+
@options = options
|
13
|
+
@options[:class] ||= 'w-8 h-8'
|
14
|
+
@email = email
|
15
|
+
end
|
16
|
+
|
17
|
+
def initials
|
18
|
+
if name.present? && !name.index('@')
|
19
|
+
name.scan(/[A-Z]/)[0..1].join
|
20
|
+
else
|
21
|
+
(name || email).split('@').map(&:capitalize).join('@').scan(/[A-Z]/)[0..1].join
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def photo_url
|
26
|
+
return unless photo&.attached?
|
27
|
+
|
28
|
+
helpers.main_app.url_for(photo)
|
29
|
+
end
|
30
|
+
|
31
|
+
def gravatar?
|
32
|
+
return false if email.blank?
|
33
|
+
|
34
|
+
url = "https://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(email).downcase}?d=404"
|
35
|
+
|
36
|
+
uri = URI.parse(url)
|
37
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
38
|
+
http.use_ssl = true if uri.scheme == 'https'
|
39
|
+
|
40
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
41
|
+
request.add_field('User-Agent', controller.request.user_agent)
|
42
|
+
response = http.request(request)
|
43
|
+
|
44
|
+
response.code.to_i != 404
|
45
|
+
end
|
46
|
+
|
47
|
+
def gravatar_url
|
48
|
+
"https://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(email).downcase}?d=404" if gravatar?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Satis
|
4
|
+
module Breadcrumbs
|
5
|
+
class Crumb < ViewComponent::Base
|
6
|
+
attr_reader :path, :title, :icon
|
7
|
+
|
8
|
+
def initialize(path:, title: nil, icon: nil)
|
9
|
+
@path = path
|
10
|
+
@title = title
|
11
|
+
@icon = icon
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Component < Satis::ApplicationComponent
|
16
|
+
|
17
|
+
renders_many :crumbs, Crumb
|
18
|
+
def initialize
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
.sts-breadcrumbs {
|
2
|
+
@apply bg-white border-b border-gray-200 flex mb-4 sm:rounded-lg sm:shadow dark:text-gray-300 dark:bg-gray-800 dark:border-gray-700;
|
3
|
+
|
4
|
+
height: 40px;
|
5
|
+
|
6
|
+
&__list {
|
7
|
+
@apply max-w-screen-xl w-full mx-auto px-4 flex space-x-4 sm:px-6 lg:px-8;
|
8
|
+
|
9
|
+
margin-left: 0;
|
10
|
+
}
|
11
|
+
|
12
|
+
&__link {
|
13
|
+
@apply ml-4 text-sm font-normal text-gray-400 hover:text-gray-500 dark:text-gray-400 dark:hover:text-gray-300;
|
14
|
+
|
15
|
+
svg {
|
16
|
+
margin-right: 0.5rem;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
li.flex
|
2
|
+
.flex.items-center
|
3
|
+
svg.flex-shrink-0.w-6.h-full.text-gray-200.dark:text-gray-700 aria-hidden="true" fill="currentColor" preserveaspectratio="none" viewbox=("0 0 24 44") xmlns="http://www.w3.org/2000/svg"
|
4
|
+
path d=("M.293 0l22 22-22 22h1.414l22-22-22-22H.293z") /
|
5
|
+
a.sts-breadcrumbs__link href=path
|
6
|
+
- if icon
|
7
|
+
i class=icon
|
8
|
+
= title
|
@@ -0,0 +1,54 @@
|
|
1
|
+
.sts-card.sts-tabs data-controller="satis-tabs" data-satis-tabs-persist-value="false"
|
2
|
+
- if header?
|
3
|
+
.sts-card__header class="#{tabs? ? '' : 'border-b border-gray-200'} #{header_background_color[:light]} dark:#{header_background_color[:dark]}"
|
4
|
+
.-ml-4.-mt-4.flex.justify-between.items-center.flex-wrap.sm:flex-nowrap
|
5
|
+
- if icon
|
6
|
+
.ml-4.mt-4.flex-shrink-0.text-primary-600.dark:text-gray-300
|
7
|
+
i class=icon
|
8
|
+
.ml-4.mt-4.flex-1
|
9
|
+
h3.text-lg.leading-6.font-medium.text-gray-900.dark:text-gray-300
|
10
|
+
== title
|
11
|
+
- if description.present?
|
12
|
+
p.mt-1.text-sm.text-gray-500.dark:text-gray-500
|
13
|
+
== description
|
14
|
+
|
15
|
+
- if actions.present? || initial_actions.present?
|
16
|
+
.ml-4.mt-4.flex-shrink-0
|
17
|
+
.grid.grid-flow-row.gap-1.sm:grid-flow-col
|
18
|
+
- for action in initial_actions
|
19
|
+
= action
|
20
|
+
- for action in actions
|
21
|
+
= action
|
22
|
+
|
23
|
+
- if menu
|
24
|
+
.ml-4.mt-4.flex-shrink-0
|
25
|
+
= render(Satis::Menu::Component.new(menu))
|
26
|
+
|
27
|
+
- if tabs?
|
28
|
+
.sts-card__tabs
|
29
|
+
.sm:hidden
|
30
|
+
label.sr-only for="tabs" Select a tab
|
31
|
+
select#tabs.block.w-full.pl-3.pr-10.py-2.text-base.border-gray-300.focus:outline-none.focus:ring-primary-500.focus:border-primary-500.sm:text-sm.rounded-md name="tabs" data-action="change->satis-tabs#select" data-satis-tabs-target="select"
|
32
|
+
- tabs.each do |tab|
|
33
|
+
option selected=tab.selected? = t(tab.name, scope: [:tabs])
|
34
|
+
.hidden.sm:block
|
35
|
+
nav.-mb-px.flex.space-x-8.overflow-x-auto aria-label="Tabs"
|
36
|
+
- tabs.each do |tab|
|
37
|
+
a.tab id="#{tab.name}" href="#" aria-current="#{tab.selected? ? "page" : ''}" data-satis-tabs-target="tab" data-action="click->satis-tabs#select"
|
38
|
+
- if tab.icon
|
39
|
+
i.mr-2 class=tab.icon
|
40
|
+
= t(tab.name, scope: [:tabs], default: tab.title || tab.name)
|
41
|
+
i.fal.fa-triangle-exclamation.ml-2.hidden
|
42
|
+
- if tab.badge
|
43
|
+
span.badge
|
44
|
+
= tab.badge
|
45
|
+
|
46
|
+
- tabs.each do |tab|
|
47
|
+
div id="#{tab.name}-content" class="tab-content #{tab.options[:padding] == true ? 'px-6 py-6' : ''}" data-satis-tabs-target="content"
|
48
|
+
= tab.to_s
|
49
|
+
- else
|
50
|
+
div class="#{content_padding ? 'px-6 py-6' : ''}"
|
51
|
+
= content
|
52
|
+
|
53
|
+
- if footer
|
54
|
+
= footer
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Card
|
2
|
+
|
3
|
+
## UI
|
4
|
+
|
5
|
+
https://tailwindui.com/components/application-ui/headings/card-headings
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
```slim
|
10
|
+
= sts.card title: 'Your profile', description: 'Edit your profile information' do |c|
|
11
|
+
- c.action
|
12
|
+
button Save
|
13
|
+
| Content here
|
14
|
+
```
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Satis
|
4
|
+
module Card
|
5
|
+
class Component < Satis::ApplicationComponent
|
6
|
+
renders_many :actions
|
7
|
+
renders_many :tabs, Tab::Component
|
8
|
+
renders_one :footer
|
9
|
+
|
10
|
+
attr_reader :icon, :title, :description, :menu, :content_padding, :header_background_color, :initial_actions
|
11
|
+
|
12
|
+
def initialize(icon: nil,
|
13
|
+
title: nil,
|
14
|
+
description: nil,
|
15
|
+
menu: nil,
|
16
|
+
content_padding: true,
|
17
|
+
header_background_color: {
|
18
|
+
dark: 'bg-gray-800', light: 'bg-white'
|
19
|
+
},
|
20
|
+
actions: [])
|
21
|
+
super
|
22
|
+
@title = title
|
23
|
+
@title = @title.reject(&:blank?).compact.join(' ') if @title.is_a?(Array)
|
24
|
+
@description = description
|
25
|
+
@icon = icon
|
26
|
+
@menu = menu
|
27
|
+
@content_padding = content_padding
|
28
|
+
@header_background_color = header_background_color
|
29
|
+
@initial_actions = actions
|
30
|
+
end
|
31
|
+
|
32
|
+
def tabs?
|
33
|
+
tabs.present?
|
34
|
+
end
|
35
|
+
|
36
|
+
def header?
|
37
|
+
icon.present? || title.present? || description.present? || menu
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
.sts-card {
|
2
|
+
@apply bg-white sm:rounded-lg sm:shadow dark:bg-gray-800 overflow-hidden;
|
3
|
+
|
4
|
+
&__header {
|
5
|
+
@apply px-4 py-5 sm:px-6 dark:border-gray-700
|
6
|
+
}
|
7
|
+
|
8
|
+
&__tabs {
|
9
|
+
@apply bg-white px-4 border-b border-gray-200 sm:px-5 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300
|
10
|
+
}
|
11
|
+
|
12
|
+
a.tab:not(.selected) {
|
13
|
+
@apply dark:text-gray-300;
|
14
|
+
}
|
15
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
div.satis-date-time-picker data-controller="satis-date-time-picker" data-satis-date-time-picker-time-picker-value=time_picker.to_s data-satis-date-time-picker-clearable-value=clearable.to_s data-satis-date-time-picker-locale-value="" data-satis-date-time-picker-range-value=range.to_s data-satis-date-time-picker-multiple-value=multiple.to_s data-satis-date-time-picker-week-start-value=week_start data-satis-date-time-picker-inline-value=inline.to_s data-satis-date-time-picker-format-value=JSON.dump(format)
|
2
|
+
.relative.flex.items-center
|
3
|
+
= form.text_field attribute, options[:input_html].merge(class: 'hidden')
|
4
|
+
input.form-control data-action="focus->satis-date-time-picker#showCalendar input->satis-date-time-picker#dateTimeEntered" data-satis-date-time-picker-target="input"
|
5
|
+
.absolute.inset-y-0.right-0.flex.py-1.5.pr-1.5
|
6
|
+
button.cursor-pointer.w-6.h-full.flex.items-center.text-gray-400.outline-none.focus:outline-none data-satis-date-time-picker-target="clearButton" data-action="click->satis-date-time-picker#clear"
|
7
|
+
i.fas.fa-xmark
|
8
|
+
|
9
|
+
.container.z-10.shadow.bg-white.border.border-gray-300.dark:bg-gray-800.dark:border-gray-700.rounded.p-4.w-72 class="#{inline ? 'inline-block' : 'hidden'}" data-satis-date-time-picker-target="calendarView"
|
10
|
+
.flex.justify-between.items-center.mb-2
|
11
|
+
div
|
12
|
+
span.text-lg.font-bold.text-gray-800.dark:text-gray-200 data-satis-date-time-picker-target="month"
|
13
|
+
span.ml-1.text-lg.text-gray-600.dark:text-gray-200.font-normal data-satis-date-time-picker-target="year"
|
14
|
+
div
|
15
|
+
button type="button" class="transition ease-in-out duration-100 inline-flex cursor-pointer hover:bg-gray-200 p-2 rounded-full" data-action="satis-date-time-picker#previousMonth"
|
16
|
+
i.fal.fa-angle-left.text-gray-500.inline-flex.px-2.py-1
|
17
|
+
button type="button" class="transition ease-in-out duration-100 inline-flex cursor-pointer hover:bg-gray-200 p-1 rounded-full" data-action="satis-date-time-picker#nextMonth"
|
18
|
+
i.fal.fa-angle-right.text-gray-500.inline-flex.px-2.py-1
|
19
|
+
|
20
|
+
|
21
|
+
.grid.grid-cols-7 data-satis-date-time-picker-target="weekDays"
|
22
|
+
template data-satis-date-time-picker-target="weekDayTemplate"
|
23
|
+
div.px-2.w-8
|
24
|
+
div.text-gray-800.dark:text-gray-200.font-medium.text-center.text-xs ${name}
|
25
|
+
|
26
|
+
.grid.grid-cols-7 data-satis-date-time-picker-target="days"
|
27
|
+
|
28
|
+
- if time_picker
|
29
|
+
.flex.items-center.py-2.space-x-2
|
30
|
+
label.flex-grow.text-sm.text-gray-500 Time
|
31
|
+
.flex.items-center.space-x-2
|
32
|
+
.bg-gray-100.rounded-md.w-full.text-right.flex.items-center.border.border-gray-100.focus:border-primary-500.focus:ring-2.focus:ring-primary-500.focus:outline-none.focus:ring-opacity-50 style=("caret-color: transparent;")
|
33
|
+
input.text-center.w-8.border-transparent.bg-transparent.p-0.h-6.text-sm.transition.duration-100.ease-in-out.border.border-transparent.focus:border-primary-500.focus:ring-2.focus:ring-primary-500.focus:outline-none.focus:ring-opacity-50.rounded inputmode="numeric" type="text" data-satis-date-time-picker-target="hours" data-action="satis-date-time-picker#changeHours keypress->satis-date-time-picker#keyPress"
|
34
|
+
span contenteditable="false"
|
35
|
+
| :
|
36
|
+
input.text-center.w-8.border-transparent.bg-transparent.p-0.h-6.text-sm.transition.duration-100.ease-in-out.border.border-transparent.focus:border-primary-500.focus:ring-2.focus:ring-primary-500.focus:outline-none.focus:ring-opacity-50.rounded inputmode="numeric" type="text" data-satis-date-time-picker-target="minutes" data-action="satis-date-time-picker#changeMinutes keypress->satis-date-time-picker#keyPress"
|
37
|
+
/span.relative.inline-flex.flex-shrink-0.transition.duration-200.ease-in-out.bg-gray-100.border.border-transparent.rounded.cursor-pointer.focus:border-primary-500.focus:ring-2.focus:ring-primary-500.focus:outline-none.focus:ring-opacity-50 aria-checked="false" role="checkbox" tabindex="0"
|
38
|
+
input type="hidden" value="AM" /
|
39
|
+
span.flex.items-center.justify-center.w-6.h-6.text-xs.text-gray-500.rounded-sm aria-hidden="true" AM
|
40
|
+
span.flex.items-center.justify-center.w-6.h-6.text-xs.text-gray-500.rounded-sm aria-hidden="true" PM
|
41
|
+
span.absolute.flex.items-center.justify-center.w-6.h-6.text-xs.text-gray-800.transition.duration-200.ease-in-out.transform.translate-x-0.bg-white.rounded.shadow aria-hidden="true" AM
|
42
|
+
/a.text-primary-600.text-sm.uppercase.font-semibold.transition.duration-100.ease-in-out.border.border-transparent.focus:border-primary-500.focus:ring-2.focus:ring-primary-500.focus:outline-none.focus:ring-opacity-50.rounded.cursor-pointer href="#" Ok
|
43
|
+
|
44
|
+
template data-satis-date-time-picker-target="emtpyTemplate"
|
45
|
+
.text-center.border.p-1.border-transparent.text-sm
|
46
|
+
template data-satis-date-time-picker-target="dayTemplate"
|
47
|
+
div
|
48
|
+
a.block.w-full.h-9.cursor-pointer.text-center.pt-2px.text-sm.rounded-l-full.rounded-r-full.leading-loose.transition.ease-in-out.duration-100.hover:bg-primary-200 data-action="satis-date-time-picker#selectDay" ${day}
|