Class: Shadcn::ApplicationViewComponent
- Inherits:
-
ViewComponent::Base
- Object
- ViewComponent::Base
- Shadcn::ApplicationViewComponent
- Includes:
- ViewComponentContrib::StyleVariants
- Defined in:
- app/components/shadcn/application_view_component.rb
Overview
Base class for every ported shadcn/ui component.
It exists to make the React → ViewComponent mapping mechanical:
React Ruby
------------------------------------ ---------------------------------------
cva(base, {variants, defaultVariants}) style { base {} variants {} defaults {} }
cn(...) TailwindMerge, wired as the style postprocessor
data-slot="card-header" slot_name :"card-header"
<div {...props} /> **attributes, splatted through Rails' tag builder
asChild as: :a / as: :span
A component that is a single element carrying data-slot + classes needs no
template at all: the inherited #call renders it. Components whose markup is
richer (an icon, a nested wrapper, an indicator) get a sidecar
component.html.erb, which overrides #call.
Direct Known Subclasses
Shadcn::Accordion::Component, Shadcn::Accordion::Content::Component, Shadcn::Accordion::Item::Component, Shadcn::Accordion::Trigger::Component, Shadcn::Alert::Component, Shadcn::AlertDialog::Action::Component, Shadcn::AlertDialog::Component, Shadcn::AlertDialog::Content::Component, Shadcn::AlertDialog::Header::Component, Shadcn::AspectRatio::Component, Shadcn::Attachment::Component, Shadcn::Attachment::Media::Component, Shadcn::Attachment::Trigger::Component, Shadcn::Avatar::Component, Shadcn::Avatar::Fallback::Component, Shadcn::Avatar::Image::Component, Badge::Component, Breadcrumb::Component, Breadcrumb::Ellipsis::Component, Breadcrumb::Page::Component, Breadcrumb::Separator::Component, Bubble::Component, Bubble::Reactions::Component, Button::Component, ButtonGroup::Component, ButtonGroup::Text::Component, Calendar::Component, Card::Component, Card::Header::Component, Carousel::Component, Carousel::Content::Component, Carousel::Item::Component, Carousel::Next::Component, Carousel::Previous::Component, Chart::Cartesian::Component, Chart::Component, Chart::Legend::Component, Chart::Pie::Component, Chart::Table::Component, Chart::Tooltip::Component, Checkbox::Component, Collapsible::Component, Collapsible::Content::Component, Collapsible::Trigger::Component, Combobox::Chip::Component, Combobox::Chips::Component, Combobox::Component, Combobox::Content::Component, Combobox::Empty::Component, Combobox::Input::Component, Combobox::Item::Component, Combobox::List::Component, Command::Component, Command::Dialog::Component, Command::Empty::Component, Command::Group::Component, Command::Input::Component, Command::Item::Component, Command::List::Component, Command::Separator::Component, ContextMenu::Component, ContextMenu::Content::Component, ContextMenu::Sub::Component, ContextMenu::Trigger::Component, Dialog::Close::Component, Dialog::Component, Dialog::Content::Component, Dialog::Header::Component, Dialog::Overlay::Component, Dialog::Trigger::Component, Drawer::Component, Drawer::Content::Component, Drawer::Header::Component, DropdownMenu::CheckboxItem::Component, DropdownMenu::Component, DropdownMenu::Content::Component, DropdownMenu::Group::Component, DropdownMenu::Item::Component, DropdownMenu::RadioGroup::Component, DropdownMenu::RadioItem::Component, DropdownMenu::Sub::Component, DropdownMenu::SubContent::Component, DropdownMenu::SubTrigger::Component, DropdownMenu::Trigger::Component, Empty::Component, Empty::Media::Component, Field::Component, Field::Error::Component, Field::Legend::Component, Field::Separator::Component, HoverCard::Component, HoverCard::Content::Component, HoverCard::Trigger::Component, Icon::Component, Input::Component, InputGroup::Addon::Component, InputGroup::Component, InputGroup::Text::Component, InputOtp::Component, InputOtp::Separator::Component, InputOtp::Slot::Component, Item::Component, Item::Group::Component, Item::Media::Component, Kbd::Component, Kbd::Group::Component, Label::Component, Marker::Component, Marker::Icon::Component, Menubar::Component, Menubar::Content::Component, Menubar::Menu::Component, Menubar::Sub::Component, Menubar::Trigger::Component, Message::Component, MessageScroller::Component, MessageScroller::Content::Component, MessageScroller::Item::Component, MessageScroller::Viewport::Component, ModeSwitcher::Component, ModeToggle::Component, NativeSelect::Component, NavigationMenu::Component, NavigationMenu::Content::Component, NavigationMenu::Trigger::Component, Pagination::Component, Pagination::Ellipsis::Component, Pagination::Link::Component, Popover::Anchor::Component, Popover::Component, Popover::Content::Component, Popover::Trigger::Component, Progress::Component, RadioGroup::Component, RadioGroup::Item::Component, Resizable::Component, Resizable::Handle::Component, Resizable::Panel::Component, ScrollArea::Component, ScrollArea::Scrollbar::Component, ScrollArea::Viewport::Component, Select::Component, Select::Content::Component, Select::Empty::Component, Select::Group::Component, Select::Item::Component, Select::List::Component, Select::Search::Component, Select::Trigger::Component, Select::Value::Component, Separator::Component, Sheet::Component, Sheet::Content::Component, Sheet::Header::Component, Sidebar::Component, Sidebar::MenuAction::Component, Sidebar::MenuButton::Component, Sidebar::MenuSkeleton::Component, Sidebar::MenuSubButton::Component, Sidebar::Provider::Component, Sidebar::Rail::Component, Sidebar::Trigger::Component, Skeleton::Component, Slider::Component, Slider::Range::Component, Slider::Thumb::Component, Slider::Track::Component, Switch::Component, Table::Component, Tabs::Component, Tabs::Content::Component, Tabs::List::Component, Tabs::Trigger::Component, Textarea::Component, ThemeSelector::Component, Toaster::Component, Toaster::Toast::Component, Toggle::Component, ToggleGroup::Component, ToggleGroup::Item::Component, Tooltip::Component, Tooltip::Content::Component, Tooltip::Provider::Component, Tooltip::Trigger::Component
Constant Summary collapse
- VOID_TAGS =
Elements that must not be given a block/closing tag.
%i[area base br col embed hr img input link meta source track wbr].freeze
- CONTENTS_STYLE =
Radix's context-only roots (Dialog, Popover, Select, …) render no element at all. We need somewhere to hang the Stimulus controller, so those roots emit a wrapper that is invisible to layout.
"display:contents"
Instance Attribute Summary collapse
-
#attributes ⇒ Object
readonly
Returns the value of attribute attributes.
Class Method Summary collapse
-
.default_tag(value = nil) ⇒ Object
The element rendered by default, i.e.
-
.slot_name(value = nil) ⇒ Object
The
data-slotvalue shadcn stamps on this part.
Instance Method Summary collapse
- #call ⇒ Object
-
#css_classes(extra = nil) ⇒ Object
Compiles the cva-equivalent style set.
-
#element_attributes(**defaults) ⇒ Object
The full attribute hash for the root element.
-
#initialize(as: nil, **attributes) ⇒ ApplicationViewComponent
constructor
A new instance of ApplicationViewComponent.
-
#merged_style(*declarations) ⇒ Object
Combines inline styles without dropping whatever the caller passed.
-
#render_element(body: nil, **defaults, &block) ⇒ Object
Renders the component's root element.
-
#shadcn_t(key, **interpolations) ⇒ Object
The user-visible strings the components render, looked up under
shadcn_view_component.*and falling back to the bundled English — so the gem works untranslated and a host app can override any key. -
#style_variants ⇒ Object
Overridden by components that expose variants (button, badge, alert, …).
- #tag_name ⇒ Object
Constructor Details
#initialize(as: nil, **attributes) ⇒ ApplicationViewComponent
Returns a new instance of ApplicationViewComponent.
52 53 54 55 |
# File 'app/components/shadcn/application_view_component.rb', line 52 def initialize(as: nil, **attributes) @as = as @attributes = attributes end |
Instance Attribute Details
#attributes ⇒ Object (readonly)
Returns the value of attribute attributes.
48 49 50 |
# File 'app/components/shadcn/application_view_component.rb', line 48 def attributes @attributes end |
Class Method Details
.default_tag(value = nil) ⇒ Object
The element rendered by default, i.e. the tag in the TSX source.
32 33 34 35 36 |
# File 'app/components/shadcn/application_view_component.rb', line 32 def default_tag(value = nil) return @default_tag = value.to_sym if value @default_tag ||= superclass.respond_to?(:default_tag) ? superclass.default_tag : :div end |
.slot_name(value = nil) ⇒ Object
The data-slot value shadcn stamps on this part. Inherited, so that
specialisations such as PaginationPrevious keep PaginationLink's slot.
40 41 42 43 44 45 |
# File 'app/components/shadcn/application_view_component.rb', line 40 def slot_name(value = nil) return @slot_name = value.to_s if value return @slot_name if defined?(@slot_name) @slot_name = superclass.respond_to?(:slot_name) ? superclass.slot_name : nil end |
Instance Method Details
#call ⇒ Object
57 58 59 |
# File 'app/components/shadcn/application_view_component.rb', line 57 def call render_element(body: content) end |
#css_classes(extra = nil) ⇒ Object
Compiles the cva-equivalent style set. Caller classes go last so tailwind-merge resolves conflicts in their favour. Parts that carry no classes of their own still have to pass the caller's through.
Without caller classes the result depends only on the component class and its variants, and the style blocks are static once loaded — so it is computed once. That is not just a saving on the merge: it keeps the library's own class strings out of tailwind_merge's LRU, which is a fixed size shared with everything the caller varies.
101 102 103 104 105 106 107 |
# File 'app/components/shadcn/application_view_component.rb', line 101 def css_classes(extra = nil) return compile_classes(extra) if extra.present? ShadcnViewComponent.class_cache.fetch_or_store([ self.class.name, style_variants ]) do compile_classes(nil) end end |
#element_attributes(**defaults) ⇒ Object
The full attribute hash for the root element.
Precedence, lowest to highest: the data-slot marker, then the component's
own defaults (what subclasses pass up through super), then whatever the
caller gave us. The caller winning is the point — it is what {...props}
does in every shadcn component, where the spread comes last.
85 86 87 88 89 90 |
# File 'app/components/shadcn/application_view_component.rb', line 85 def element_attributes(**defaults) base = { "data-slot" => self.class.slot_name }.compact merged = merge_attributes(base, defaults, attributes) merged["class"] = css_classes(merged.delete("class")) merged.compact end |
#merged_style(*declarations) ⇒ Object
Combines inline styles without dropping whatever the caller passed.
136 137 138 |
# File 'app/components/shadcn/application_view_component.rb', line 136 def merged_style(*declarations) [ attributes[:style], *declarations ].compact.reject(&:empty?).join(";").presence end |
#render_element(body: nil, **defaults, &block) ⇒ Object
Renders the component's root element. defaults are the attributes the
component itself contributes; see #element_attributes for how they rank
against the caller's.
The body comes either from a block (templates: <%= render_element do %>)
or from body: — #call passes ViewComponent's already-captured content
that way, because re-capturing it here would swallow it.
72 73 74 75 76 77 |
# File 'app/components/shadcn/application_view_component.rb', line 72 def render_element(body: nil, **defaults, &block) attrs = element_attributes(**defaults) return tag.public_send(tag_name, **attrs) if VOID_TAGS.include?(tag_name) content_tag(tag_name, block ? capture(&block) : body, attrs) end |
#shadcn_t(key, **interpolations) ⇒ Object
The user-visible strings the components render, looked up under
shadcn_view_component.* and falling back to the bundled English — so the
gem works untranslated and a host app can override any key.
The fallback is the locale: :en default below, and it has to be there.
This method used to be a bare I18n.t while the comment above it already
claimed the English was a default; it was not, and the gem only looked
untranslatable-proof because every app it had been tried in ran in
English. A host running it got I18n::MissingTranslationData out of a
searchable select — and with config.i18n.raise_on_missing_translations,
which the Rails generators turn on in development and test, a raised page
rather than a fallback string.
A lambda, so the second lookup only happens when the first misses.
128 129 130 131 132 133 |
# File 'app/components/shadcn/application_view_component.rb', line 128 def shadcn_t(key, **interpolations) full_key = "shadcn_view_component.#{key}" I18n.t(full_key, **interpolations, default: ->(*) { I18n.t(full_key, locale: :en, **interpolations) }) end |
#style_variants ⇒ Object
Overridden by components that expose variants (button, badge, alert, …).
110 111 112 |
# File 'app/components/shadcn/application_view_component.rb', line 110 def style_variants {} end |
#tag_name ⇒ Object
61 62 63 |
# File 'app/components/shadcn/application_view_component.rb', line 61 def tag_name (@as || self.class.default_tag).to_sym end |