satis 1.0.66

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