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.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +92 -0
  4. data/Rakefile +23 -0
  5. data/app/assets/config/satis_manifest.js +1 -0
  6. data/app/assets/stylesheets/satis/application.css +15 -0
  7. data/app/components/satis/appearance_switcher/component.html.slim +6 -0
  8. data/app/components/satis/appearance_switcher/component.rb +11 -0
  9. data/app/components/satis/appearance_switcher/component.scss +34 -0
  10. data/app/components/satis/appearance_switcher/component_controller.js +62 -0
  11. data/app/components/satis/application_component.rb +50 -0
  12. data/app/components/satis/avatar/component.html.slim +7 -0
  13. data/app/components/satis/avatar/component.rb +52 -0
  14. data/app/components/satis/breadcrumbs/component.html.slim +8 -0
  15. data/app/components/satis/breadcrumbs/component.rb +23 -0
  16. data/app/components/satis/breadcrumbs/component.scss +19 -0
  17. data/app/components/satis/breadcrumbs/crumb.slim +8 -0
  18. data/app/components/satis/card/component.html.slim +54 -0
  19. data/app/components/satis/card/component.md +14 -0
  20. data/app/components/satis/card/component.rb +41 -0
  21. data/app/components/satis/card/component.scss +15 -0
  22. data/app/components/satis/date_time_picker/component.html.slim +48 -0
  23. data/app/components/satis/date_time_picker/component.md +11 -0
  24. data/app/components/satis/date_time_picker/component.rb +48 -0
  25. data/app/components/satis/date_time_picker/component.scss +5 -0
  26. data/app/components/satis/date_time_picker/component_controller.js +499 -0
  27. data/app/components/satis/dropdown/component.html.slim +36 -0
  28. data/app/components/satis/dropdown/component.md +48 -0
  29. data/app/components/satis/dropdown/component.rb +77 -0
  30. data/app/components/satis/dropdown/component.scss +10 -0
  31. data/app/components/satis/dropdown/component_controller.js +547 -0
  32. data/app/components/satis/flash_messages/component.html.slim +3 -0
  33. data/app/components/satis/flash_messages/component.rb +31 -0
  34. data/app/components/satis/flash_messages/component.scss +18 -0
  35. data/app/components/satis/flash_messages/message.html.slim +8 -0
  36. data/app/components/satis/info/component.html.slim +4 -0
  37. data/app/components/satis/info/component.rb +22 -0
  38. data/app/components/satis/info_item/component.html.slim +7 -0
  39. data/app/components/satis/info_item/component.rb +19 -0
  40. data/app/components/satis/input/component.html.slim +11 -0
  41. data/app/components/satis/input/component.rb +38 -0
  42. data/app/components/satis/input/component.scss +50 -0
  43. data/app/components/satis/input/element.html.slim +2 -0
  44. data/app/components/satis/map/component.html.slim +2 -0
  45. data/app/components/satis/map/component.rb +17 -0
  46. data/app/components/satis/map/component.scss +9 -0
  47. data/app/components/satis/map/component_controller.js +37 -0
  48. data/app/components/satis/menu/component.html.slim +13 -0
  49. data/app/components/satis/menu/component.md +1 -0
  50. data/app/components/satis/menu/component.rb +16 -0
  51. data/app/components/satis/menu/component_controller.js +62 -0
  52. data/app/components/satis/menu_item/component.html.slim +16 -0
  53. data/app/components/satis/menu_item/component.rb +14 -0
  54. data/app/components/satis/page/component.html.slim +45 -0
  55. data/app/components/satis/page/component.rb +15 -0
  56. data/app/components/satis/page/component_controller.js +86 -0
  57. data/app/components/satis/sidebar_menu/component.html.slim +3 -0
  58. data/app/components/satis/sidebar_menu/component.rb +17 -0
  59. data/app/components/satis/sidebar_menu/component.scss +0 -0
  60. data/app/components/satis/sidebar_menu/component_controller.js +9 -0
  61. data/app/components/satis/sidebar_menu/mobile/component.html.slim +3 -0
  62. data/app/components/satis/sidebar_menu/mobile/component.rb +10 -0
  63. data/app/components/satis/sidebar_menu_item/component.html.slim +15 -0
  64. data/app/components/satis/sidebar_menu_item/component.rb +20 -0
  65. data/app/components/satis/sidebar_menu_item/component.scss +27 -0
  66. data/app/components/satis/sidebar_menu_item/component_controller.js +62 -0
  67. data/app/components/satis/sidebar_menu_item/mobile/component.html.slim +17 -0
  68. data/app/components/satis/sidebar_menu_item/mobile/component.rb +10 -0
  69. data/app/components/satis/switch/component.html.slim +14 -0
  70. data/app/components/satis/switch/component.rb +24 -0
  71. data/app/components/satis/switch/component_controller.js +49 -0
  72. data/app/components/satis/tab/component.rb +35 -0
  73. data/app/components/satis/tabs/component.html.slim +23 -0
  74. data/app/components/satis/tabs/component.md +21 -0
  75. data/app/components/satis/tabs/component.rb +16 -0
  76. data/app/components/satis/tabs/component.scss +33 -0
  77. data/app/components/satis/tabs/component_controller.js +123 -0
  78. data/app/controllers/satis/application_controller.rb +4 -0
  79. data/app/helpers/satis/application_helper.rb +15 -0
  80. data/app/jobs/satis/application_job.rb +4 -0
  81. data/app/mailers/satis/application_mailer.rb +6 -0
  82. data/app/models/satis/application_record.rb +5 -0
  83. data/app/views/shared/_fields_for.html.slim +35 -0
  84. data/config/routes.rb +5 -0
  85. data/lib/satis/action_controller_helpers.rb +29 -0
  86. data/lib/satis/configuration.rb +61 -0
  87. data/lib/satis/engine.rb +27 -0
  88. data/lib/satis/forms/builder.rb +440 -0
  89. data/lib/satis/forms/concerns/buttons.rb +49 -0
  90. data/lib/satis/forms/concerns/file.rb +35 -0
  91. data/lib/satis/forms/concerns/options.rb +44 -0
  92. data/lib/satis/forms/concerns/required.rb +68 -0
  93. data/lib/satis/forms/concerns/select.rb +95 -0
  94. data/lib/satis/helpers/container.rb +83 -0
  95. data/lib/satis/menus/builder.rb +13 -0
  96. data/lib/satis/menus/item.rb +34 -0
  97. data/lib/satis/menus/menu.rb +23 -0
  98. data/lib/satis/version.rb +3 -0
  99. data/lib/satis.rb +36 -0
  100. data/lib/tasks/satis_tasks.rake +4 -0
  101. 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Satis
4
+ module AppearanceSwitcher
5
+ class Component < Satis::ApplicationComponent
6
+ def initialize(**options)
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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,8 @@
1
+ nav.sts-breadcrumbs aria-label="Breadcrumb"
2
+ ol.sts-breadcrumbs__list
3
+ li.flex
4
+ .flex.items-center
5
+ a.text-gray-400.hover:text-gray-500 href=root_path
6
+ i.fas.fa-house
7
+ - crumbs.each do |crumb|
8
+ = crumb
@@ -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}
@@ -0,0 +1,11 @@
1
+ # Date Time Picker
2
+
3
+ ## UI
4
+
5
+ https://www.vue-tailwind.com/docs/datepicker/
6
+
7
+ ## Usage
8
+
9
+ ```slim
10
+ = f.input :created_at, as: :date_time_picker
11
+ ```