Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ae211a1
Bump fontawesome to 7
tdegrunt Jul 31, 2025
8680fff
Remove fontawesome free
tdegrunt Oct 3, 2025
70cbdea
Add upgrade notice
tdegrunt Oct 3, 2025
f769614
Bump satis to 2.1.64
tdegrunt Oct 3, 2025
0fc80d3
Bump satis to 2.2.0
tdegrunt Oct 3, 2025
161d395
2.3.0
tdegrunt Oct 3, 2025
bb82d5b
Bump satis to 2.3.0
tdegrunt Oct 3, 2025
89b0b25
Wording
tdegrunt Oct 3, 2025
6fc0b67
dropdown show hide
shubhrathasetty Jan 13, 2026
b76af53
Merge pull request #202 from entdec/bugfix/fix_show_hide_for_dropdown
shubhrathasetty Mar 10, 2026
906db10
Bump satis to 2.3.1
shubhrathasetty Mar 10, 2026
38ee230
Bump satis to 2.3.2
shubhrathasetty Mar 10, 2026
ee3a101
minor fix
shubhrathasetty Mar 16, 2026
8857594
select option if single matching value found and fix chainto dropdown
shubhrathasetty Apr 7, 2026
c0c7eed
feature/SidebarImprovementsUsability
TwistedClone Apr 7, 2026
06d6f7a
Merge pull request #203 from entdec/feature/SidebarImprovementsUsability
TwistedClone Apr 7, 2026
4da52ac
feature/CardImprovementsConfigurable
TwistedClone Apr 8, 2026
519d154
Merge pull request #204 from entdec/bugfix/packscreen_drop_down_issues
shubhrathasetty Apr 14, 2026
911f008
fix auto select - fields for dropdown
shubhrathasetty Apr 16, 2026
8e9f513
Merge pull request #205 from entdec/feature/CardImprovementsConfigurable
shubhrathasetty Apr 16, 2026
55b6f3c
Merge pull request #206 from entdec/bugfix/packscreen_auto_selec_valu…
shubhrathasetty Apr 20, 2026
6868b31
Bump satis to 2.3.3
shubhrathasetty May 5, 2026
e291501
Bump satis to 2.3.4
shubhrathasetty May 5, 2026
f78bfa7
Bump satis to 2.3.5
shubhrathasetty May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ We use:

- [TailwindCSS](https://tailwindcss.com)
- [TailwindUI](https://tailwindui.com)
- [FontAwesome 6](https://fontawesome.com/v6.0/)
- [FontAwesome 7](https://fontawesome.com/v7.0/)
- [ViewComponent](https://viewcomponent.org)
- [HotWired](https://hotwired.dev)
- [BEM](https://cssguidelin.es/#bem-like-naming)

## Upgrade

* From 2.3.0 onwards, we will no longer ship Fontawesome free. It conflicts with Fontawesome Pro. You can easily add the javascript files, you can download them from here: https://fontawesome.com/download. Use the files from the `js` folder. Put these in a folder which is in your manifest and it should just work.

## Installation

Your main app needs to have at least the following config/postcss.config.js:
Expand Down
1 change: 0 additions & 1 deletion app/assets/config/satis_manifest.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//= link_tree ../images
//= link_directory ../fontawesome
//= link_directory ../../javascript/satis .js
//= link_directory ../../javascript/satis/controllers .js
//= link_directory ../../javascript/satis/elements .js
Expand Down
6 changes: 0 additions & 6 deletions app/assets/fontawesome/brands.js

This file was deleted.

6 changes: 0 additions & 6 deletions app/assets/fontawesome/fontawesome.js

This file was deleted.

6 changes: 0 additions & 6 deletions app/assets/fontawesome/solid.js

This file was deleted.

8 changes: 8 additions & 0 deletions app/components/satis/card/component.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
@apply px-4 py-5 sm:px-6 dark:bg-gray-900 bg-white dark:border-gray-700 dark:text-gray-300;
}

&__header--compact {
@apply px-3 py-3 sm:px-4;
}

&[data-satis-card-collapsible-value="true"] .sts-card__header {
@apply cursor-pointer select-none;
}

&__tabs {
@apply bg-white px-4 border-b border-gray-200 sm:px-5 bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300;

Expand Down
33 changes: 20 additions & 13 deletions app/components/satis/card/component.html.slim
Original file line number Diff line number Diff line change
@@ -1,38 +1,45 @@
.sts-card data-controller="satis-tabs" data-satis-tabs-persist-value=persist data-satis-tabs-key-value=key id=identifier
.sts-card data-controller="satis-card satis-tabs" data-satis-tabs-persist-value=persist data-satis-tabs-key-value=key data-satis-card-collapsible-value=collapsible data-satis-card-collapsed-value=collapsed data-satis-card-identifier-value=identifier id=identifier
- if header?
.sts-card__header class="#{tabs? ? '' : 'border-b border-gray-200'}"
.sts-card__header class=header_classes data-action="#{collapsible ? 'click->satis-card#toggle' : ''}"
.-ml-4.-mt-4.flex.justify-between.items-center.flex-wrap.sm:flex-nowrap
- if icon
.ml-4.mt-4.flex-shrink-0.text-primary-600.dark:text-gray-300
i class=icon
.ml-4.mt-4.flex-1
h3.text-lg.leading-6.font-medium.text-gray-900.dark:text-white
h3 class=title_classes
= title
- if description.present?
p.mt-1.text-sm.text-gray-500.dark:text-gray-500
= description

- if actions.present? || initial_actions.present?
.ml-4.mt-4.flex-shrink-0
.ml-4.mt-4.flex-shrink-0 data-satis-card-target="actions"
.grid.grid-flow-row.gap-1.sm:grid-flow-col
- initial_actions.each do |action|
= action
- actions.each do |action|
= action

- if collapsible
.ml-4.mt-4.flex-shrink-0
button.text-gray-400.hover:text-gray-600.dark:hover:text-gray-200.transition-transform.duration-200 type="button" data-satis-card-target="collapseIcon"
i.fas.fa-chevron-down

- if menu
.ml-4.mt-2.flex-shrink-0
= render(Satis::Menu::Component.new(menu))

- if tabs?
= render Satis::Tabs::Component.new(custom_link: custom_tabs_link).tap {|c| c.original_view_context = view_context } do |t|
- tabs.each_with_index do |ta, i|
- t.with_tab ta.name, id: ta.id, icon: ta.icon, padding: ta.padding, badge: ta.badge, menu: ta.menu, dirty: ta.dirty, title: ta.title, responsive: ta.responsive, selected_tab_index: ta.selected_tab_index do
= ta
div data-satis-card-target="body"
- if tabs?
= render Satis::Tabs::Component.new(custom_link: custom_tabs_link).tap {|c| c.original_view_context = view_context } do |t|
- tabs.each_with_index do |ta, i|
- t.with_tab ta.name, id: ta.id, icon: ta.icon, padding: ta.padding, badge: ta.badge, menu: ta.menu, dirty: ta.dirty, title: ta.title, responsive: ta.responsive, selected_tab_index: ta.selected_tab_index do
= ta

- else
div class="#{content_padding ? 'px-6 py-6' : ''}"
= content
- else
div class=content_classes style=content_style
= content

- if footer
= footer
div data-satis-card-target="footer"
= footer
51 changes: 49 additions & 2 deletions app/components/satis/card/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ class Component < Satis::ApplicationComponent
renders_many :tabs, Tab::Component
renders_one :footer

attr_reader :identifier, :icon, :description, :menu, :content_padding, :header_background_color, :initial_actions, :persist, :key
attr_reader :identifier, :icon, :description, :menu, :content_padding, :header_background_color, :initial_actions, :persist, :key,
:collapsible, :collapsed, :height, :min_height, :max_height, :padding, :compact
attr_writer :scope

def initialize(identifier = nil,
Expand All @@ -23,7 +24,14 @@ def initialize(identifier = nil,
scope: [],
actions: [],
key: nil,
persist: true)
persist: true,
collapsible: false,
collapsed: false,
height: nil,
min_height: nil,
max_height: nil,
padding: nil,
compact: false)
super

if identifier.blank?
Expand All @@ -43,6 +51,13 @@ def initialize(identifier = nil,
@key = key
@custom_tabs_link = custom_tabs_link
@scope = scope.present? ? scope : identifier
@collapsible = collapsible
@collapsed = collapsed
@height = height
@min_height = min_height
@max_height = max_height
@padding = padding
@compact = compact
end

# def key
Expand Down Expand Up @@ -70,6 +85,38 @@ def custom_tabs_link(&block)
def header?
icon.present? || title.present? || description.present? || menu
end

def content_style
styles = []
styles << "height: #{height}" if height
styles << "min-height: #{min_height}" if min_height
styles << "max-height: #{max_height}; overflow-y: auto" if max_height
styles.join('; ')
end

def content_classes
classes = []
if padding
classes << padding
elsif content_padding
classes << (compact ? 'px-4 py-3' : 'px-6 py-6')
end
classes.join(' ')
end

def header_classes
base = tabs? ? '' : 'border-b border-gray-200'
base += ' sts-card__header--compact' if compact
base
end

def title_classes
if compact
'text-sm leading-5 font-medium text-gray-900 dark:text-white'
else
'text-lg leading-6 font-medium text-gray-900 dark:text-white'
end
end
end
end
end
78 changes: 78 additions & 0 deletions app/components/satis/card/component_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["body", "collapseIcon", "actions", "footer"]
static values = {
collapsible: { type: Boolean, default: false },
collapsed: { type: Boolean, default: false },
identifier: String
}

connect() {
if (this.collapsibleValue) {
this._restoreState()
this._applyState(false)
}
}

toggle(event) {
if (!this.collapsibleValue) return

// Don't toggle when clicking on actions, buttons, links, or menus inside the header
if (event.target.closest('a, button:not([data-satis-card-target="collapseIcon"]), .sts-menu, [data-action]')) {
if (!event.target.closest('[data-satis-card-target="collapseIcon"]')) return
}

this.collapsedValue = !this.collapsedValue
this._applyState(true)
this._saveState()
}

_applyState(animate) {
const collapsed = this.collapsedValue

if (this.hasBodyTarget) {
if (animate) {
this.bodyTarget.style.transition = "max-height 0.2s ease-in-out, opacity 0.2s ease-in-out"
}

if (collapsed) {
this.bodyTarget.style.maxHeight = "0px"
this.bodyTarget.style.opacity = "0"
this.bodyTarget.style.overflow = "hidden"
} else {
this.bodyTarget.style.maxHeight = ""
this.bodyTarget.style.opacity = ""
this.bodyTarget.style.overflow = ""
}
}

if (this.hasFooterTarget) {
this.footerTarget.style.display = collapsed ? "none" : ""
}

// Actions stay visible when collapsed — they're in the header
if (this.hasCollapseIconTarget) {
this.collapseIconTarget.style.transform = collapsed ? "rotate(-90deg)" : "rotate(0deg)"
}
}

_storageKey() {
return `satis-card-${this.identifierValue}-collapsed`
}

_saveState() {
if (this.identifierValue) {
sessionStorage.setItem(this._storageKey(), this.collapsedValue)
}
}

_restoreState() {
if (this.identifierValue) {
const stored = sessionStorage.getItem(this._storageKey())
if (stored !== null) {
this.collapsedValue = stored === "true"
}
}
}
}
6 changes: 3 additions & 3 deletions app/components/satis/dropdown/component.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ div.satis-dropdown data-action="keydown->satis-dropdown#dispatch" data-controlle
div.hidden.py-1 data-satis-dropdown-target="pills"
.flex.flex-col.items-center
.w-full.sts-dropdown class=input_class
.h-12.p-1.flex.rounded
div class=input_wrapper_classes
.flex.flex-auto.flex-wrap.sts-dropdown-input
/ Input where you can search
input.focus:ring-0.border-none.p-1.px-2.appearance-none.w-full.sts-dropdown-input.text-gray-800.dark:text-gray-300 data-action="input->satis-dropdown#search" data-satis-dropdown-target="searchInput" placeholder=placeholder autofocus=options[:autofocus]
input class=search_input_classes data-action="input->satis-dropdown#search" data-satis-dropdown-target="searchInput" placeholder=placeholder autofocus=options[:autofocus]
div
/ Reset button
- unless @reset_button == false
Expand All @@ -28,7 +28,7 @@ div.satis-dropdown data-action="keydown->satis-dropdown#dispatch" data-controlle
template data-satis-dropdown-target="selectedItemsTemplate"

/ Container for results
.hidden.container.sts-dropdown-results.shadow.dark:text-gray-300.z-10.rounded.max-h-select.overflow-y-auto.w-full data-satis-dropdown-target="results" data-action="scroll->satis-dropdown#scroll" tabindex="-1"
div class=results_classes style=results_style data-satis-dropdown-target="results" data-action="scroll->satis-dropdown#scroll" tabindex="-1"
.flex.flex-col.w-full data-satis-dropdown-target="items"
- options[:collection]&.each do |item|
- data_attrs = item.try(:third) ? item.third : {}
Expand Down
32 changes: 31 additions & 1 deletion app/components/satis/dropdown/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Satis
module Dropdown
class Component < ViewComponent::Base
attr_reader :url, :form, :attribute, :title, :options
attr_reader :url, :form, :attribute, :title, :options, :dropdown_max_height, :dropdown_min_height, :dropdown_width, :dropdown_height, :compact

def initialize(form:, attribute:, **options, &block)
super
Expand All @@ -18,6 +18,11 @@ def initialize(form:, attribute:, **options, &block)
@needs_exact_match = options[:needs_exact_match]
@reset_button = options[:reset_button] || options[:include_blank]
@toggle_button = options[:toggle_button] != false
@dropdown_max_height = options[:max_height]
@dropdown_min_height = options[:min_height]
@dropdown_width = options[:width]
@dropdown_height = options[:height]
@compact = options[:compact] || false

options[:input_html] ||= {}

Expand Down Expand Up @@ -113,6 +118,31 @@ def item_html(item)
def input_class
[@options.fetch(:input_html, {}).fetch(:class, ""), form.has_error?(attribute) ? "is-invalid" : ""].join(" ")
end

def results_style
styles = []
styles << "max-height: #{dropdown_max_height}" if dropdown_max_height
styles << "min-height: #{dropdown_min_height}" if dropdown_min_height
styles << "width: #{dropdown_width}" if dropdown_width
styles << "height: #{dropdown_height}" if dropdown_height
styles.join('; ')
end

def results_classes
classes = "hidden container sts-dropdown-results shadow dark:text-gray-300 z-10 rounded overflow-y-auto w-full"
classes += " max-h-select" unless dropdown_max_height
classes
end

def input_wrapper_classes
compact ? 'h-9 p-1 flex rounded' : 'h-12 p-1 flex rounded'
end

def search_input_classes
base = 'focus:ring-0 border-none p-1 px-2 appearance-none w-full sts-dropdown-input text-gray-800 dark:text-gray-300'
base += ' text-sm' if compact
base
end
end
end
end
12 changes: 7 additions & 5 deletions app/components/satis/dropdown/component_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ export default class DropdownComponentController extends ApplicationController {
}

setHiddenSelect() {
if (this.element.querySelector('select[name*="[TEMPLATE]"]')) {
return
}
if (this.hiddenSelectTarget.options.length === 0) {
this.searchInputTarget.value = ""
this.pillsTarget.innerHTML = ""
Expand Down Expand Up @@ -542,7 +545,7 @@ export default class DropdownComponentController extends ApplicationController {
// auto select if there is only one match and we are not in freetext mode
if (!this.freeTextValue) {
if (matches.length === 1) {
if (this.filteredSearchQuery.length >= this.minSearchQueryLengthValue &&
if (this.filteredSearchQuery?.length >= this.minSearchQueryLengthValue &&
matches[0].getAttribute("data-satis-dropdown-item-text").toLowerCase().indexOf(this.lastSearch.toLowerCase()) >= 0) {
const dataDiv = matches[0].closest('[data-satis-dropdown-target="item"]')
this.selectItem(dataDiv)
Expand Down Expand Up @@ -601,15 +604,14 @@ export default class DropdownComponentController extends ApplicationController {
this.fetchResultsWith(ourUrl).then((itemCount) => {
if (this.hasResults) {
this.filterResultsChainTo()

if (!this.resultsShown && !this.chainToValue) {
if (!this.resultsShown) {
this.showResultsList()
}

// auto select when there is only 1 value
if (this.filteredSearchQuery.length >= this.minSearchQueryLengthValue && this.nrOfItems === 1 && !this.freeTextValue) {
if (this.filteredSearchQuery?.length >= this.minSearchQueryLengthValue && this.nrOfItems === 1 && !this.freeTextValue) {
const dataDiv = this.itemTargets[0].closest('[data-satis-dropdown-target="item"]')
this.selectItem(dataDiv)
this.selectItem(dataDiv, true)
this.setSelectedItem(dataDiv.getAttribute("data-satis-dropdown-item-value"))
this.searchQueryValue = ""
} else if (this.searchQueryValue?.length > 0) {
Expand Down
Loading
Loading