Skip to content

Commit dcad2f0

Browse files
committed
add sidebar wrapper
1 parent 25cab04 commit dcad2f0

File tree

6 files changed

+122
-119
lines changed

6 files changed

+122
-119
lines changed

lib/ruby_ui/sidebar/collapsiable_sidebar.rb

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,25 @@
22

33
module RubyUI
44
class CollapsiableSidebar < Base
5-
def initialize(side: :left, variant: :sidebar, **attrs)
5+
SIDEBAR_WIDTH = '16rem'
6+
SIDEBAR_WIDTH_ICON = '3rem'
7+
8+
def initialize(side: :left, variant: :sidebar, collapsible: :offcanvas, open: true, **attrs)
69
@side = side
710
@variant = variant
11+
@collapsible = collapsible
12+
@open = open
813
super(**attrs)
914
end
1015

1116
def view_template(&)
1217
div(**attrs) do
13-
div(**gap_element_attrs)
14-
div(**content_wrapper_attrs) do
15-
div(**content_attrs, &)
18+
MobileSidebar(side: @side, &)
19+
div(**wrapper_attrs) do
20+
div(**gap_element_attrs)
21+
div(**content_wrapper_attrs) do
22+
div(**content_attrs, &)
23+
end
1624
end
1725
end
1826
end
@@ -21,17 +29,32 @@ def view_template(&)
2129

2230
def default_attrs
2331
{
24-
class: "peer hidden text-sidebar-foreground md:block"
32+
class: 'group/sidebar',
33+
style: "--sidebar-width: #{SIDEBAR_WIDTH}; --sidebar-width-icon: #{SIDEBAR_WIDTH_ICON};",
34+
data: {
35+
state: @open ? 'expanded' : 'collapsed',
36+
collapsible: @open ? '' : @collapsible,
37+
variant: @variant,
38+
side: @side,
39+
collapsible_kind: @collapsible,
40+
ruby_ui__sidebar_target: 'sidebar'
41+
}
42+
}
43+
end
44+
45+
def wrapper_attrs
46+
{
47+
class: 'peer hidden text-sidebar-foreground md:block'
2548
}
2649
end
2750

2851
def gap_element_attrs
2952
{
3053
class: [
31-
"relative w-[--sidebar-width] bg-transparent transition-[width]",
32-
"duration-200 ease-linear",
33-
"group-data-[collapsible=offcanvas]/sidebar:w-0",
34-
"group-data-[side=right]/sidebar:rotate-180",
54+
'relative w-[--sidebar-width] bg-transparent transition-[width]',
55+
'duration-200 ease-linear',
56+
'group-data-[collapsible=offcanvas]/sidebar:w-0',
57+
'group-data-[side=right]/sidebar:rotate-180',
3558
variant_classes
3659
]
3760
}
@@ -40,8 +63,8 @@ def gap_element_attrs
4063
def content_wrapper_attrs
4164
{
4265
class: [
43-
"fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width]",
44-
"transition-[left,right,width] duration-200 ease-linear md:flex",
66+
'fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width]',
67+
'transition-[left,right,width] duration-200 ease-linear md:flex',
4568
content_wrapper_side_classes,
4669
content_wrapper_variant_classes
4770
]
@@ -51,37 +74,37 @@ def content_wrapper_attrs
5174
def content_attrs
5275
{
5376
class: [
54-
"flex h-full w-full flex-col bg-sidebar",
55-
"group-data-[variant=floating]/sidebar:rounded-lg",
56-
"group-data-[variant=floating]/sidebar:border",
57-
"group-data-[variant=floating]/sidebar:border-sidebar-border",
58-
"group-data-[variant=floating]/sidebar:shadow"
77+
'flex h-full w-full flex-col bg-sidebar',
78+
'group-data-[variant=floating]/sidebar:rounded-lg',
79+
'group-data-[variant=floating]/sidebar:border',
80+
'group-data-[variant=floating]/sidebar:border-sidebar-border',
81+
'group-data-[variant=floating]/sidebar:shadow'
5982
],
6083
data: {
61-
sidebar: "sidebar"
84+
sidebar: 'sidebar'
6285
}
6386
}
6487
end
6588

6689
def variant_classes
6790
if %i[floating inset].include?(@variant)
68-
"group-data-[collapsible=icon]/sidebar:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
91+
'group-data-[collapsible=icon]/sidebar:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
6992
else
70-
"group-data-[collapsible=icon]/sidebar:w-[--sidebar-width-icon]"
93+
'group-data-[collapsible=icon]/sidebar:w-[--sidebar-width-icon]'
7194
end
7295
end
7396

7497
def content_wrapper_side_classes
75-
return "left-0 group-data-[collapsible=offcanvas]/sidebar:left-[calc(var(--sidebar-width)*-1)]" if @side == :left
98+
return 'left-0 group-data-[collapsible=offcanvas]/sidebar:left-[calc(var(--sidebar-width)*-1)]' if @side == :left
7699

77-
"right-0 group-data-[collapsible=offcanvas]/sidebar:right-[calc(var(--sidebar-width)*-1)]"
100+
'right-0 group-data-[collapsible=offcanvas]/sidebar:right-[calc(var(--sidebar-width)*-1)]'
78101
end
79102

80103
def content_wrapper_variant_classes
81104
if %i[floating inset].include?(@variant)
82-
"p-2 group-data-[collapsible=icon]/sidebar:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
105+
'p-2 group-data-[collapsible=icon]/sidebar:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
83106
else
84-
"group-data-[collapsible=icon]/sidebar:w-[--sidebar-width-icon] group-data-[side=left]/sidebar:border-r group-data-[side=right]/sidebar:border-l"
107+
'group-data-[collapsible=icon]/sidebar:w-[--sidebar-width-icon] group-data-[side=left]/sidebar:border-r group-data-[side=right]/sidebar:border-l'
85108
end
86109
end
87110
end

lib/ruby_ui/sidebar/mobile_sidebar.rb

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module RubyUI
44
class MobileSidebar < Base
5-
SIDEBAR_WIDTH_MOBILE = "18rem"
5+
SIDEBAR_WIDTH_MOBILE = '18rem'
66

77
def initialize(side: :left, **attrs)
88
@side = side
@@ -13,20 +13,20 @@ def view_template(&)
1313
Sheet(**attrs) do
1414
SheetContent(
1515
side: @side,
16-
class: "w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden",
16+
class: 'w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden',
1717
style: {
1818
"--sidebar-width": SIDEBAR_WIDTH_MOBILE
1919
},
2020
data: {
21-
sidebar: "sidebar",
22-
mobile: "true"
23-
},
21+
sidebar: 'sidebar',
22+
mobile: 'true'
23+
}
2424
) do
25-
SheetHeader(class: "sr-only") do
26-
SheetTitle { "Sidebar" }
27-
SheetDescription { "Displays the mobile sidebar." }
25+
SheetHeader(class: 'sr-only') do
26+
SheetTitle { 'Sidebar' }
27+
SheetDescription { 'Displays the mobile sidebar.' }
2828
end
29-
div(class: "flex h-full w-full flex-col", &)
29+
div(class: 'flex h-full w-full flex-col', &)
3030
end
3131
end
3232
end
@@ -35,7 +35,10 @@ def view_template(&)
3535

3636
def default_attrs
3737
{
38-
class: "ruby-ui--sidebar-sheet"
38+
data: {
39+
ruby_ui__sidebar_target: 'mobileSidebar',
40+
action: 'ruby--ui-sidebar:open->ruby-ui--sheet#open:self'
41+
}
3942
}
4043
end
4144
end

lib/ruby_ui/sidebar/sidebar.rb

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@
22

33
module RubyUI
44
class Sidebar < Base
5-
SIDEBAR_WIDTH = "16rem"
6-
SIDEBAR_WIDTH_ICON = "3rem"
7-
85
SIDES = %i[left right].freeze
96
VARIANTS = %i[sidebar floating inset].freeze
107
COLLAPSIBLES = %i[offcanvas icon none].freeze
118

129
def initialize(side: :left, variant: :sidebar, collapsible: :offcanvas, open: true, **attrs)
13-
raise ArgumentError, "Invalid side: #{side}. Must be one of #{SIDES}." unless SIDES.include?(side.to_sym)
14-
raise ArgumentError, "Invalid variant: #{variant}. Must be one of #{VARIANTS}." unless VARIANTS.include?(variant.to_sym)
15-
raise ArgumentError, "Invalid collapsible: #{collapsible}. Must be one of #{COLLAPSIBLES}." unless COLLAPSIBLES.include?(collapsible.to_sym)
10+
raise ArgumentError, "Invalid side: #{side}." unless SIDES.include?(side.to_sym)
11+
raise ArgumentError "Invalid variant: #{variant}." unless VARIANTS.include?(variant.to_sym)
12+
raise ArgumentError, "Invalid collapsible: #{collapsible}." unless COLLAPSIBLES.include?(collapsible.to_sym)
1613

1714
@side = side.to_sym
1815
@variant = variant.to_sym
@@ -26,29 +23,9 @@ def view_template(&)
2623
if @collapsible == :none
2724
NonCollapsiableSidebar(&)
2825
else
29-
MobileSidebar(side: @side, &)
30-
CollapsiableSidebar(side: @side, variant: @variant, &)
26+
CollapsiableSidebar(side: @side, variant: @variant, collapsible: @collapsible, open: @open, &)
3127
end
3228
end
3329
end
34-
35-
private
36-
37-
def default_attrs
38-
{
39-
class: "peer group/sidebar has-[[data-variant=inset]]:bg-sidebar",
40-
style: "--sidebar-width: #{SIDEBAR_WIDTH}; --sidebar-width-icon: #{SIDEBAR_WIDTH_ICON};",
41-
data: {
42-
controller: "ruby-ui--sidebar",
43-
state: @open ? "expanded" : "collapsed",
44-
collapsible: @open ? "" : @collapsible,
45-
variant: @variant,
46-
side: @side,
47-
ruby_ui__sidebar_open_value: @open.to_s,
48-
ruby_ui__sidebar_collapsible_value: @collapsible,
49-
ruby_ui__sidebar_ruby_ui__sheet_outlet: ".ruby-ui--sidebar-sheet"
50-
}
51-
}
52-
end
5330
end
5431
end

lib/ruby_ui/sidebar/sidebar_controller.js

Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,85 +2,66 @@ import { Controller } from "@hotwired/stimulus";
22

33
const SIDEBAR_COOKIE_NAME = "sidebar_state";
44
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
5-
const TRIGGER_SELECTOR = "[data-sidebar-trigger]";
65
const State = {
76
EXPANDED: "expanded",
87
COLLAPSED: "collapsed",
98
};
109
const MOBILE_BREAKPOINT = 768;
1110

1211
export default class extends Controller {
13-
static outlets = ["ruby-ui--sheet"];
14-
static values = {
15-
open: {
16-
type: Boolean,
17-
default: true,
18-
},
19-
collapsible: {
20-
type: String,
21-
default: "offcanvas",
22-
},
23-
};
24-
25-
connect() {
26-
this.#setupSidebarTriggers();
27-
}
12+
static targets = ["sidebar", "mobileSidebar"];
13+
14+
sidebarTargetConnected() {
15+
const { state, collapsibleKind } = this.sidebarTarget.dataset;
2816

29-
disconnect() {
30-
this.#removeSidebarTriggers();
17+
this.open = state === State.EXPANDED;
18+
this.collapsibleKind = collapsibleKind;
3119
}
3220

33-
toggle() {
21+
toggle(e) {
22+
e.preventDefault();
23+
3424
if (this.#isMobile()) {
3525
this.#openMobileSidebar();
3626

3727
return;
3828
}
3929

40-
this.openValue = !this.openValue;
30+
this.open = !this.open;
31+
this.onToggle();
4132
}
4233

43-
openValueChanged() {
34+
onToggle() {
4435
this.#updateSidebarState();
4536
this.#persistSidebarState();
4637
}
4738

48-
#setupSidebarTriggers() {
49-
this.#siderbarTriggerElements().forEach((trigger) => {
50-
trigger.addEventListener("click", this.toggle.bind(this));
51-
});
52-
}
53-
54-
#removeSidebarTriggers() {
55-
this.#siderbarTriggerElements().forEach((trigger) => {
56-
trigger.removeEventListener("click", this.toggle.bind(this));
57-
});
58-
}
59-
60-
#siderbarTriggerElements() {
61-
return document.querySelectorAll(TRIGGER_SELECTOR);
62-
}
63-
6439
#updateSidebarState() {
65-
const { dataset } = this.element;
40+
if (!this.hasSidebarTarget) {
41+
return;
42+
}
43+
44+
const { dataset } = this.sidebarTarget;
6645

67-
dataset.state = this.openValue ? State.EXPANDED : State.COLLAPSED;
68-
dataset.collapsible = this.openValue ? "" : this.collapsibleValue;
46+
dataset.state = this.open ? State.EXPANDED : State.COLLAPSED;
47+
dataset.collapsible = this.open ? "" : this.collapsibleKind;
6948
}
7049

7150
#persistSidebarState() {
72-
document.cookie = `${SIDEBAR_COOKIE_NAME}=${this.openValue}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
51+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${this.open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
7352
}
7453

7554
#isMobile() {
7655
return window.innerWidth < MOBILE_BREAKPOINT;
7756
}
7857

7958
#openMobileSidebar() {
80-
if (!this.rubyUiSheetOutlet) {
59+
if (!this.hasMobileSidebarTarget) {
8160
return;
8261
}
8362

84-
this.rubyUiSheetOutlet.open();
63+
this.mobileSidebarTarget.dispatchEvent(
64+
new CustomEvent("ruby--ui-sidebar:open"),
65+
);
8566
}
8667
}

lib/ruby_ui/sidebar/sidebar_trigger.rb

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,37 @@ class SidebarTrigger < Base
55
def view_template(&)
66
Button(variant: :ghost, size: :icon, **attrs) do
77
panel_left_icon
8-
span(class: "sr-only") { "Toggle Sidebar" }
8+
span(class: 'sr-only') { 'Toggle Sidebar' }
99
end
1010
end
1111

1212
private
1313

1414
def default_attrs
1515
{
16-
class: "h-7 w-7 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
16+
class: 'h-7 w-7 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
1717
data: {
18-
sidebar: "trigger",
19-
sidebar_trigger: true
18+
sidebar: 'trigger',
19+
action: 'click->ruby-ui--sidebar#toggle'
2020
}
2121
}
2222
end
2323

2424
def panel_left_icon
2525
svg(
26-
xmlns: "http://www.w3.org/2000/svg",
27-
width: "24",
28-
height: "24",
29-
viewBox: "0 0 24 24",
30-
fill: "none",
31-
stroke: "currentColor",
32-
stroke_width: "2",
33-
stroke_linecap: "round",
34-
stroke_linejoin: "round",
35-
class: "lucide lucide-panel-left"
26+
xmlns: 'http://www.w3.org/2000/svg',
27+
width: '24',
28+
height: '24',
29+
viewBox: '0 0 24 24',
30+
fill: 'none',
31+
stroke: 'currentColor',
32+
stroke_width: '2',
33+
stroke_linecap: 'round',
34+
stroke_linejoin: 'round',
35+
class: 'lucide lucide-panel-left'
3636
) do |s|
37-
s.rect(width: "18", height: "18", x: "3", y: "3", rx: "2")
38-
s.path(d: "M9 3v18")
37+
s.rect(width: '18', height: '18', x: '3', y: '3', rx: '2')
38+
s.path(d: 'M9 3v18')
3939
end
4040
end
4141
end

0 commit comments

Comments
 (0)