Module: UI::SheetContentBehavior

Included in:
SheetContent, SheetContentComponent
Defined in:
app/behaviors/ui/sheet_content_behavior.rb

Overview

Shared behavior for Sheet Content component Handles side variants and slide animations

Constant Summary collapse

SIDES =
%w[top right bottom left].freeze

Instance Method Summary collapse

Instance Method Details

#merged_sheet_content_data_attributesObject

Merge user-provided data attributes



66
67
68
69
# File 'app/behaviors/ui/sheet_content_behavior.rb', line 66

def merged_sheet_content_data_attributes
  user_data = @attributes&.fetch(:data, {}) || {}
  user_data.merge(sheet_content_data_attributes)
end

#sheet_content_base_classesObject

Base CSS classes for sheet content (all sides) Match shadcn exactly - slide animations with proper pointer-events



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'app/behaviors/ui/sheet_content_behavior.rb', line 12

def sheet_content_base_classes
  [
    # Base structure (same as shadcn) - shadow only when open to avoid shadow bleeding when closed
    "bg-background fixed z-50 flex flex-col gap-4",
    "data-[state=open]:shadow-lg data-[state=closed]:shadow-none",
    # Pointer events control - prevent interaction when closed
    "data-[state=open]:pointer-events-auto data-[state=closed]:pointer-events-none",
    # Transition timing (same as shadcn)
    "transition ease-in-out",
    "data-[state=closed]:duration-300 data-[state=open]:duration-500",
    # Animation (same as shadcn)
    "data-[state=open]:animate-in data-[state=closed]:animate-out",
    # Prevent exit animation on page load (our addition)
    "data-[initial]:animate-none data-[initial]:invisible"
  ].join(" ")
end

#sheet_content_classesObject

Merge base classes with side classes and custom classes



50
51
52
53
54
55
56
# File 'app/behaviors/ui/sheet_content_behavior.rb', line 50

def sheet_content_classes
  TailwindMerge::Merger.new.merge([
    sheet_content_base_classes,
    sheet_content_side_classes,
    @classes
  ].compact.join(" "))
end

#sheet_content_close_button_classesObject

CSS classes for the built-in close button



89
90
91
# File 'app/behaviors/ui/sheet_content_behavior.rb', line 89

def sheet_content_close_button_classes
  "ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"
end

#sheet_content_data_attributesObject

Data attributes for Stimulus target



59
60
61
62
63
# File 'app/behaviors/ui/sheet_content_behavior.rb', line 59

def sheet_content_data_attributes
  {
    ui__dialog_target: "content"
  }
end

#sheet_content_html_attributesObject

Build complete HTML attributes hash for sheet content



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'app/behaviors/ui/sheet_content_behavior.rb', line 72

def sheet_content_html_attributes
  base_attrs = @attributes&.except(:data) || {}
  attrs = base_attrs.merge(
    class: sheet_content_classes,
    role: "dialog",
    "aria-modal": "true",
    "data-state": @open ? "open" : "closed",
    data: merged_sheet_content_data_attributes
  )
  # Add data-initial when closed to prevent exit animations on page load
  attrs["data-initial"] = "" unless @open
  # Add inert when closed to prevent focus on elements inside
  attrs[:inert] = true unless @open
  attrs
end

#sheet_content_side_classesObject

Side-specific CSS classes Include translate classes to keep content off-screen when closed (after animation ends)



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'app/behaviors/ui/sheet_content_behavior.rb', line 31

def sheet_content_side_classes
  side = @side || "right"

  case side.to_s
  when "right"
    "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right data-[state=closed]:translate-x-full data-[state=open]:translate-x-0 inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm"
  when "left"
    "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left data-[state=closed]:-translate-x-full data-[state=open]:translate-x-0 inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm"
  when "top"
    "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top data-[state=closed]:-translate-y-full data-[state=open]:translate-y-0 inset-x-0 top-0 h-auto border-b"
  when "bottom"
    "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom data-[state=closed]:translate-y-full data-[state=open]:translate-y-0 inset-x-0 bottom-0 h-auto border-t"
  else
    # Default to right
    "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right data-[state=closed]:translate-x-full data-[state=open]:translate-x-0 inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm"
  end
end