Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
8606c61
Setup sidebar
lsouoliveira Mar 20, 2025
813366f
Add the sidebar group component
lsouoliveira Mar 20, 2025
ecc143a
Add sidebar group label
lsouoliveira Mar 20, 2025
54c6075
Add the sidebar group content componeent
lsouoliveira Mar 20, 2025
5c70609
add sidebar menu
lsouoliveira Mar 20, 2025
505b4a7
add sidebar menu item
lsouoliveira Mar 20, 2025
b3d6d6b
rename group
lsouoliveira Mar 21, 2025
7fff06a
add sidebar submenu item
lsouoliveira Mar 21, 2025
024391c
add sidebar header
lsouoliveira Mar 21, 2025
49b9375
add sidebar footer
lsouoliveira Mar 21, 2025
c645462
add sidebar group action
lsouoliveira Mar 21, 2025
80e7b57
add sidebar menu action
lsouoliveira Mar 21, 2025
af7b3de
fix state
lsouoliveira Mar 21, 2025
bd85ef3
add menu badge component
lsouoliveira Mar 21, 2025
d153edd
break long class strings into shorter ones
lsouoliveira Mar 22, 2025
3c404fb
add sidebar menu sub
lsouoliveira Mar 22, 2025
f0dffe4
add sidebar menu sub item
lsouoliveira Mar 22, 2025
a0e8b4f
fix variant instance not being set
lsouoliveira Mar 22, 2025
0627ec1
add sidebar menu sub button
lsouoliveira Mar 22, 2025
525d263
add sidebar menu skeleton
lsouoliveira Mar 22, 2025
d8aa789
rename sidebar-wrapper group to sidebar
lsouoliveira Mar 22, 2025
689e43a
add the sidebar trigger component
lsouoliveira Mar 22, 2025
1fb7eb2
add the sidebar controller
lsouoliveira Mar 22, 2025
b4c3358
setup the sidebar controller
lsouoliveira Mar 22, 2025
6f17507
add a open flag
lsouoliveira Mar 22, 2025
b3fea80
fix collapsible value
lsouoliveira Mar 22, 2025
ac23fd3
persist sidebar state in a cookie
lsouoliveira Mar 22, 2025
ce5c958
fix icon attributes
lsouoliveira Mar 22, 2025
cc6e7d7
lint files
lsouoliveira Mar 22, 2025
cc64a32
rename sidebar trigger data identifier
lsouoliveira Mar 22, 2025
01fcede
add the sidebar rail component
lsouoliveira Mar 22, 2025
2f9b931
add the sidebar input component
lsouoliveira Mar 22, 2025
7abe27f
convert attributes to symbol
lsouoliveira Mar 22, 2025
fc00680
add sidebar inset component
lsouoliveira Mar 22, 2025
952fcb6
setup the mobile sidebar
lsouoliveira Mar 22, 2025
25cab04
remove todo comments
lsouoliveira Mar 22, 2025
dcad2f0
add sidebar wrapper
lsouoliveira Mar 23, 2025
c9c5eb9
move sidebar to another component
lsouoliveira Mar 23, 2025
819b34d
fix sidebar rail
lsouoliveira Mar 23, 2025
13e22d2
fix inset styling
lsouoliveira Mar 23, 2025
c87a95c
add tests
lsouoliveira Mar 23, 2025
eca5186
fix lint
lsouoliveira Mar 24, 2025
fa64b69
add sidebar separator component
lsouoliveira Mar 25, 2025
0107a41
add separator to tests
lsouoliveira Mar 25, 2025
cf0e94d
fix broken variables
lsouoliveira Mar 26, 2025
3ebad7c
add .to_s to boolean data attributes
lsouoliveira Mar 26, 2025
7814ba3
lint code
lsouoliveira Mar 27, 2025
9172804
replace public_send with tag
lsouoliveira Mar 27, 2025
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
99 changes: 99 additions & 0 deletions lib/ruby_ui/sidebar/collapsiable_sidebar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# frozen_string_literal: true

module RubyUI
class CollapsiableSidebar < Base
def initialize(side: :left, variant: :sidebar, collapsible: :offcanvas, open: true, **attrs)
@side = side
@variant = variant
@collapsible = collapsible
@open = open
super(**attrs)
end

def view_template(&)
MobileSidebar(side: @side, **attrs, &)
div(**mix(sidebar_attrs, attrs)) do
div(**gap_element_attrs)
div(**content_wrapper_attrs) do
div(**content_attrs, &)
end
end
end

private

def sidebar_attrs
{
class: "group peer hidden text-sidebar-foreground md:block",
data: {
state: @open ? "expanded" : "collapsed",
collapsible: @open ? "" : @collapsible,
variant: @variant,
side: @side,
collapsible_kind: @collapsible,
ruby_ui__sidebar_target: "sidebar"
}
}
end

def gap_element_attrs
{
class: [
"relative w-[var(--sidebar-width)] bg-transparent transition-[width]",
"duration-200 ease-linear",
"group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180",
variant_classes
]
}
end

def content_wrapper_attrs
{
class: [
"fixed inset-y-0 z-10 hidden h-svh w-[var(--sidebar-width)]",
"transition-[left,right,width] duration-200 ease-linear md:flex",
content_wrapper_side_classes,
content_wrapper_variant_classes
]
}
end

def content_attrs
{
class: [
"flex h-full w-full flex-col bg-sidebar",
"group-data-[variant=floating]:rounded-lg",
"group-data-[variant=floating]:border",
"group-data-[variant=floating]:border-sidebar-border",
"group-data-[variant=floating]:shadow"
],
data: {
sidebar: "sidebar"
}
}
end

def variant_classes
if %i[floating inset].include?(@variant)
"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
else
"group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)]"
end
end

def content_wrapper_side_classes
return "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" if @side == :left

"right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]"
end

def content_wrapper_variant_classes
if %i[floating inset].include?(@variant)
"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
else
"group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)] group-data-[side=left]:border-r group-data-[side=right]:border-l"
end
end
end
end
45 changes: 45 additions & 0 deletions lib/ruby_ui/sidebar/mobile_sidebar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

module RubyUI
class MobileSidebar < Base
SIDEBAR_WIDTH_MOBILE = "18rem"

def initialize(side: :left, **attrs)
@side = side
super(**attrs)
end

def view_template(&)
Sheet(**attrs) do
SheetContent(
side: @side,
class: "w-[var(--sidebar-width)] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden",
style: {
"--sidebar-width": SIDEBAR_WIDTH_MOBILE
},
data: {
sidebar: "sidebar",
mobile: "true"
}
) do
SheetHeader(class: "sr-only") do
SheetTitle { "Sidebar" }
SheetDescription { "Displays the mobile sidebar." }
end
div(class: "flex h-full w-full flex-col", &)
end
end
end

private

def default_attrs
{
data: {
ruby_ui__sidebar_target: "mobileSidebar",
action: "ruby--ui-sidebar:open->ruby-ui--sheet#open:self"
}
}
end
end
end
17 changes: 17 additions & 0 deletions lib/ruby_ui/sidebar/non_collapsible_sidebar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module RubyUI
class NonCollpapsibleSidebar < Base
def view_template(&)
div(**attrs, &)
end

private

def default_attrs
{
class: "flex h-full w-[var(--sidebar-width)] flex-col bg-sidebar text-sidebar-foreground"
}
end
end
end
29 changes: 29 additions & 0 deletions lib/ruby_ui/sidebar/sidebar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module RubyUI
class Sidebar < Base
SIDES = %i[left right].freeze
VARIANTS = %i[sidebar floating inset].freeze
COLLAPSIBLES = %i[offcanvas icon none].freeze

def initialize(side: :left, variant: :sidebar, collapsible: :offcanvas, open: true, **attrs)
raise ArgumentError, "Invalid side: #{side}." unless SIDES.include?(side.to_sym)
raise ArgumentError "Invalid variant: #{variant}." unless VARIANTS.include?(variant.to_sym)
raise ArgumentError, "Invalid collapsible: #{collapsible}." unless COLLAPSIBLES.include?(collapsible.to_sym)

@side = side.to_sym
@variant = variant.to_sym
@collapsible = collapsible.to_sym
@open = open
super(**attrs)
end

def view_template(&)
if @collapsible == :none
NonCollapsiableSidebar(**attrs, &)
else
CollapsiableSidebar(side: @side, variant: @variant, collapsible: @collapsible, open: @open, **attrs, &)
end
end
end
end
20 changes: 20 additions & 0 deletions lib/ruby_ui/sidebar/sidebar_content.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module RubyUI
class SidebarContent < Base
def view_template(&)
div(**attrs, &)
end

private

def default_attrs
{
class: "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
data: {
sidebar: "content"
}
}
end
end
end
67 changes: 67 additions & 0 deletions lib/ruby_ui/sidebar/sidebar_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Controller } from "@hotwired/stimulus";

const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const State = {
EXPANDED: "expanded",
COLLAPSED: "collapsed",
};
const MOBILE_BREAKPOINT = 768;

export default class extends Controller {
static targets = ["sidebar", "mobileSidebar"];

sidebarTargetConnected() {
const { state, collapsibleKind } = this.sidebarTarget.dataset;

this.open = state === State.EXPANDED;
this.collapsibleKind = collapsibleKind;
}

toggle(e) {
e.preventDefault();

if (this.#isMobile()) {
this.#openMobileSidebar();

return;
}

this.open = !this.open;
this.onToggle();
}

onToggle() {
this.#updateSidebarState();
this.#persistSidebarState();
}

#updateSidebarState() {
if (!this.hasSidebarTarget) {
return;
}

const { dataset } = this.sidebarTarget;

dataset.state = this.open ? State.EXPANDED : State.COLLAPSED;
dataset.collapsible = this.open ? "" : this.collapsibleKind;
}

#persistSidebarState() {
document.cookie = `${SIDEBAR_COOKIE_NAME}=${this.open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
}

#isMobile() {
return window.innerWidth < MOBILE_BREAKPOINT;
}

#openMobileSidebar() {
if (!this.hasMobileSidebarTarget) {
return;
}

this.mobileSidebarTarget.dispatchEvent(
new CustomEvent("ruby--ui-sidebar:open"),
);
}
}
20 changes: 20 additions & 0 deletions lib/ruby_ui/sidebar/sidebar_footer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module RubyUI
class SidebarFooter < Base
def view_template(&)
div(**attrs, &)
end

private

def default_attrs
{
class: "flex flex-col gap-2 p-2",
data: {
sidebar: "footer"
}
}
end
end
end
20 changes: 20 additions & 0 deletions lib/ruby_ui/sidebar/sidebar_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module RubyUI
class SidebarGroup < Base
def view_template(&)
div(**attrs, &)
end

private

def default_attrs
{
class: "relative flex w-full min-w-0 flex-col p-2",
data: {
sidebar: "group"
}
}
end
end
end
33 changes: 33 additions & 0 deletions lib/ruby_ui/sidebar/sidebar_group_action.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module RubyUI
class SidebarGroupAction < Base
def initialize(as: :button, **attrs)
@as = as
super(**attrs)
end

def view_template(&)
tag(@as, **attrs, &)
end

private

def default_attrs
{
class: [
"absolute right-3 top-3.5 flex aspect-square w-5 items-center",
"justify-center rounded-md p-0 text-sidebar-foreground",
"outline-none ring-sidebar-ring transition-transform",
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
"focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"after:absolute after:-inset-2 after:md:hidden",
"group-data-[collapsible=icon]:hidden"
],
data: {
sidebar: "group-action"
}
}
end
end
end
20 changes: 20 additions & 0 deletions lib/ruby_ui/sidebar/sidebar_group_content.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module RubyUI
class SidebarGroupContent < Base
def view_template(&)
div(**attrs, &)
end

private

def default_attrs
{
class: "w-full text-sm",
data: {
sidebar: "group-content"
}
}
end
end
end
Loading