Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Component page + basic Design system #468

Merged
merged 11 commits into from
Dec 10, 2024
Merged
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ GEM
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
rbs (3.6.1)
rbs (3.7.0)
logger
rdoc (6.8.1)
psych (>= 4.0.0)
Expand Down Expand Up @@ -505,7 +505,7 @@ GEM
fugit (~> 1.11.0)
railties (>= 7.1)
thor (~> 1.3.1)
sorbet-runtime (0.5.11663)
sorbet-runtime (0.5.11681)
sqlite3 (2.2.0-aarch64-linux-gnu)
sqlite3 (2.2.0-arm64-darwin)
sqlite3 (2.2.0-x86_64-darwin)
Expand Down
1 change: 1 addition & 0 deletions app/assets/images/icons/fontawesome/arrow-right-solid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/images/icons/fontawesome/share-nodes-solid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@import "tailwindcss/components";
@import "tailwindcss/utilities";

@import "components/button.css";
@import "components/diff.css";
@import "components/dropdown.css";
@import "components/form.css";
Expand Down
30 changes: 30 additions & 0 deletions app/assets/stylesheets/components/button.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@layer components {
.btn {
@apply font-normal;
@apply fill-current;
}

.btn:not(.btn-sm) {
height: 2.5rem;
min-height: 2.5rem;
}
/* for the toolbar buttons */
.btn.btn-pill.btn-outline.btn-sm {
@apply rounded-full border border-gray-300 bg-white hover:bg-gray-200/50 hover:text-base-content/80;
@apply text-sm font-normal;
@apply flex-nowrap whitespace-nowrap;

/* for the watched button */
&.active {
@apply border-primary bg-primary text-primary-content;
}
}

.btn.btn-secondary {
@apply whitespace-nowrap border-primary bg-white fill-primary text-primary hover:bg-gray-100 hover:text-primary/90;
}

.btn.btn-neutral {
@apply bg-white font-normal hover:fill-neutral/80 hover:text-neutral/80;
}
}
7 changes: 7 additions & 0 deletions app/assets/stylesheets/components/form.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@
label {
@apply font-semibold;
}

/* because setting --rounded-btn to 1.9rem makes also all input field fully rounded */
/* we override this behaviour */
.input,
.textarea {
@apply rounded-lg border border-gray-200 bg-white;
}
}
4 changes: 4 additions & 0 deletions app/assets/stylesheets/components/modal.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
@layer components {
.modal-box {
@apply bg-white;
}

.modal-top.spotlight {
.modal-box {
@apply absolute left-0 right-0 top-4 mx-4 w-auto rounded-lg lg:top-8 lg:mx-8;
Expand Down
8 changes: 2 additions & 6 deletions app/components/ui/button_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

class Ui::ButtonComponent < ApplicationComponent
KIND_MAPPING = {
neutral: "btn-neutral",
primary: "btn-primary",
secondary: "btn-secondary",
accent: "btn-accent",
info: "btn-info",
success: "btn-success",
warning: "btn-warning",
error: "btn-error",
neutral: "btn-neutral btn-outline",
pill: "btn btn-pill btn-outline btn-sm",
ghost: "btn-ghost",
link: "btn-link",
none: ""
Expand Down
2 changes: 1 addition & 1 deletion app/components/ui/modal_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<%= content_tag :dialog, role: :dialog, aria: {modal: true}, class: classes, **attributes do %>
<div class="modal-box" data-modal-target="modalBox">
<div class="modal-box <%= size_class %>" data-modal-target="modalBox">
<% if close_button %>
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" autofocus="false" data-action="click->modal#close">
<%= helpers.heroicon :x_mark %>
Expand Down
10 changes: 10 additions & 0 deletions app/components/ui/modal_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ class Ui::ModalComponent < ApplicationComponent
responsive: "modal-bottom sm:modal-middle"
}

SIZE_MAPPING = {
md: "",
lg: "!max-w-[800px]"
}

option :open, type: Dry::Types["strict.bool"], default: proc { false }
option :close_button, type: Dry::Types["strict.bool"], default: proc { true }
option :position, type: Dry::Types["coercible.symbol"].enum(*POSITION_MAPPING.keys), optional: true, default: proc { :responsive }
option :size, type: Dry::Types["coercible.symbol"].enum(*SIZE_MAPPING.keys), optional: true, default: proc { :md }

private

Expand All @@ -37,6 +43,10 @@ def component_classes
)
end

def size_class
SIZE_MAPPING[size]
end

def content_classes
class_names("dropdown-content menu menu-sm p-2 mt-4 w-max z-[1] rounded-lg shadow-2xl bg-white text-neutral", attributes.delete(:content_classes))
end
Expand Down
9 changes: 9 additions & 0 deletions app/controllers/concerns/remote_modal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module RemoteModal

included do
layout :define_layout
helper_method :modal_options
end

class_methods do
Expand All @@ -19,6 +20,14 @@ def enable_remote_modal
@remote_modal = true
end

def modal_options
@modal_options ||= {}
end

def set_modal_options(options)
@modal_options = options
end

def define_layout
return "modal" if turbo_frame_request_id == "modal" && @remote_modal

Expand Down
3 changes: 3 additions & 0 deletions app/controllers/page_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@ def home

def featured
end

def components
end
end
5 changes: 5 additions & 0 deletions app/controllers/talks_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
class TalksController < ApplicationController
include RemoteModal
include Pagy::Backend
include WatchedTalks
skip_before_action :authenticate_user!

respond_with_remote_modal only: [:edit]

before_action :set_talk, only: %i[show edit update]
before_action :set_user_favorites, only: %i[index show]

Expand All @@ -23,6 +27,7 @@ def show

# GET /talks/1/edit
def edit
set_modal_options(size: :lg)
end

# PATCH/PUT /talks/1
Expand Down
10 changes: 5 additions & 5 deletions app/helpers/icon_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

module IconHelper
SIZE_CLASSES = {
xs: "h-4 w-4",
sm: "h-5 w-5",
md: "h-6 w-6",
lg: "h-8 w-8",
xl: "h-10 w-10"
xs: "h-4",
sm: "h-5",
md: "h-6",
lg: "h-8",
xl: "h-10"
}

def heroicon(icon_name, size: :md, variant: :outline, **options)
Expand Down
18 changes: 18 additions & 0 deletions app/helpers/view_component_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,22 @@ module ViewComponentHelper
render component.constantize.new(*args, **kwargs), &block
end
end

def ui_tooltip(text, &block)
tag.div data: {controller: "tooltip", tooltip_content_value: text} do
yield
end
end

def external_link_to(text, url = nil, **attributes, &)
if block_given?
url = text
text = capture(&)
end

classes = class_names("link inline-flex items-center gap-2 flex-nowrap", attributes.delete(:class))
link_to url, class: classes, target: "_blank", rel: "noopener noreferrer", **attributes do
concat(content_tag(:span) { text }).concat(fa("arrow-up-right-from-square", size: :xs))
end
end
end
3 changes: 3 additions & 0 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ application.register("splide", SplideController)
import SpotlightSearchController from "./spotlight_search_controller"
application.register("spotlight-search", SpotlightSearchController)

import TabsController from "./tabs_controller"
application.register("tabs", TabsController)

import TalksNavigationController from "./talks_navigation_controller"
application.register("talks-navigation", TalksNavigationController)

Expand Down
10 changes: 10 additions & 0 deletions app/javascript/controllers/modal_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export default class extends Controller {

initialize () {
useClickOutside(this, { element: this.modalBoxTarget })
if (this.toggle) {
this.toggle.addEventListener('click', () => {
this.open()
})
}
}

connect () {
Expand All @@ -37,4 +42,9 @@ export default class extends Controller {

this.element.close()
}

// getters
get toggle () {
return document.querySelector(`[data-toggle="modal"][data-target="${this.element.id}"]`)
}
}
46 changes: 46 additions & 0 deletions app/javascript/controllers/tabs_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Controller } from '@hotwired/stimulus'

// Connects to data-controller="tabs"
export default class extends Controller {
static values = { activeIndex: { type: Number, default: 0 } }

initialize () {
if (!this.hasActiveIndexValue) {
const index = Array.from(this.tabs).findIndex(tab => tab.getAttribute('aria-selected') === 'true' || tab.classList.contains('tab-active'))
this.activeIndexValue = index
}
}

showPanel (e) {
const index = Array.from(this.tabs).indexOf(e.currentTarget)
this.activeIndexValue = index
}

activeIndexValueChanged () {
this.#togglePanel()
}

// private

#togglePanel () {
this.tabs.forEach(tab => {
const isSelected = tab === this.activeTab
tab.setAttribute('aria-selected', isSelected)
document.startViewTransition(() => {
tab.classList.toggle('tab-active', isSelected)
})
})
}

get tabPanels () {
return this.element.querySelectorAll('[role="tabpanel"]')
}

get tabs () {
return this.element.querySelectorAll('[role="tab"]')
}

get activeTab () {
return this.tabs[this.activeIndexValue]
}
}
Loading
Loading