From 92d779b9c8c34db423129133ec3fbb7229052159 Mon Sep 17 00:00:00 2001 From: Jordan Burke Date: Fri, 17 Apr 2026 14:12:06 -0400 Subject: [PATCH 1/5] feat: add IconComponent with Feather icon set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Shipwright::IconComponent renders inline SVG icons from a manifest of Feather Icons (https://feathericons.com, MIT licensed). Matches Shipwright Pro's Figma icon set (node 2:163). Starter manifest includes 21 commonly-needed icons: info, alert-circle, alert-triangle, check, check-circle, x, x-circle, chevron-{up,down,left,right}, plus, minus, search, menu, external-link, settings, arrow-{left,right}, help-circle, trash. Icons live in app/components/shipwright/icons.rb as a frozen constant hash of name => inner-SVG fragment. Adding a new icon = paste Feather's inner markup into the hash. API: <%= render Shipwright::IconComponent.new(name: :info) %> <%= render Shipwright::IconComponent.new(name: :check, size: :lg, class: "text-utility-negative-default") %> <%= render Shipwright::IconComponent.new(name: :info, "aria-label": "More info") %> Props: - name: required symbol/string matching a key in ICONS - size: :sm (16px), :md (24px, default), :lg (32px) - class: consumer override - **html_attrs: pass-through (id, data-*, aria-*, etc.) Icons use stroke="currentColor" so they inherit the parent's text color — consumers control color via text-* utilities. Decorative by default (aria-hidden=true); providing aria-label makes the icon meaningful and skips aria-hidden. 13 new component tests pass. Total suite: 38 tests, 81 assertions. Lookbook previews: default, gallery (all 21), sizes, colored, with_aria_label. --- app/components/shipwright/icon_component.rb | 55 ++++++++++++ app/components/shipwright/icons.rb | 32 +++++++ .../shipwright/icon_component_preview.rb | 28 +++++++ .../icon_component_preview/colored.html.erb | 6 ++ .../icon_component_preview/gallery.html.erb | 8 ++ .../icon_component_preview/sizes.html.erb | 5 ++ .../shipwright/icon_component_test.rb | 84 +++++++++++++++++++ test/dummy/app/assets/builds/tailwind.css | 2 +- .../dummy/app/views/pages/components.html.erb | 17 ++++ 9 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 app/components/shipwright/icon_component.rb create mode 100644 app/components/shipwright/icons.rb create mode 100644 test/components/previews/shipwright/icon_component_preview.rb create mode 100644 test/components/previews/shipwright/icon_component_preview/colored.html.erb create mode 100644 test/components/previews/shipwright/icon_component_preview/gallery.html.erb create mode 100644 test/components/previews/shipwright/icon_component_preview/sizes.html.erb create mode 100644 test/components/shipwright/icon_component_test.rb diff --git a/app/components/shipwright/icon_component.rb b/app/components/shipwright/icon_component.rb new file mode 100644 index 0000000..5c91570 --- /dev/null +++ b/app/components/shipwright/icon_component.rb @@ -0,0 +1,55 @@ +require_relative "icons" + +module Shipwright + class IconComponent < BaseComponent + SIZES = { + sm: "w-4 h-4", # 16px + md: "w-6 h-6", # 24px — matches Feather native viewBox + lg: "w-8 h-8" # 32px + }.freeze + + class << self + # Exposed for tests / discoverability. Returns symbol keys. + def icon_names + @icon_names ||= ICONS.keys.map(&:to_sym).freeze + end + end + + # `class:` is a Ruby reserved word used as a keyword arg — requires + # binding.local_variable_get. Shipwright convention. + def initialize(name:, size: :md, class: nil, **html_attrs) + @name = name.to_s + @size = size + @class = binding.local_variable_get(:class) + @html_attrs = html_attrs + end + + def call + paths = ICONS.fetch(@name) do + raise ArgumentError, "#{self.class}: unknown icon :#{@name}. Available: #{ICONS.keys.sort.join(', ')}" + end + size_classes = SIZES.fetch(@size) do |v| + raise ArgumentError, "#{self.class}: unknown size :#{v}. Valid: #{SIZES.keys.join(', ')}" + end + + # Icons without an explicit aria-label are decorative and hidden from + # screen readers. When a consumer provides aria-label, treat the icon + # as meaningful and skip aria-hidden. + aria_label = @html_attrs[:"aria-label"] || @html_attrs[:aria_label] + accessibility_attrs = aria_label ? {} : { "aria-hidden": "true" } + + content_tag :svg, + paths, + class: classes(size_classes, consumer: @class), + xmlns: "http://www.w3.org/2000/svg", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + "stroke-width": "2", + "stroke-linecap": "round", + "stroke-linejoin": "round", + **accessibility_attrs, + **@html_attrs + end + end +end diff --git a/app/components/shipwright/icons.rb b/app/components/shipwright/icons.rb new file mode 100644 index 0000000..1667596 --- /dev/null +++ b/app/components/shipwright/icons.rb @@ -0,0 +1,32 @@ +module Shipwright + # Feather Icons (https://feathericons.com) — MIT licensed, 24x24 viewBox, + # stroke-based design. Values are the inner SVG fragment; the wrapping + # with viewBox/stroke attributes is provided by IconComponent. + # + # To add an icon: copy its inner content from https://feathericons.com/ + # and paste it below. Keep icon names in kebab-case matching Figma's + # icon name in the design file. + ICONS = { + "info" => %().html_safe, + "alert-circle" => %().html_safe, + "alert-triangle" => %().html_safe, + "check" => %().html_safe, + "check-circle" => %().html_safe, + "x" => %().html_safe, + "x-circle" => %().html_safe, + "chevron-down" => %().html_safe, + "chevron-up" => %().html_safe, + "chevron-left" => %().html_safe, + "chevron-right" => %().html_safe, + "plus" => %().html_safe, + "minus" => %().html_safe, + "search" => %().html_safe, + "menu" => %().html_safe, + "external-link" => %().html_safe, + "settings" => %().html_safe, + "arrow-right" => %().html_safe, + "arrow-left" => %().html_safe, + "help-circle" => %().html_safe, + "trash" => %().html_safe + }.freeze +end diff --git a/test/components/previews/shipwright/icon_component_preview.rb b/test/components/previews/shipwright/icon_component_preview.rb new file mode 100644 index 0000000..0bc4272 --- /dev/null +++ b/test/components/previews/shipwright/icon_component_preview.rb @@ -0,0 +1,28 @@ +module Shipwright + class IconComponentPreview < Lookbook::Preview + # @label Default (info, md) + def default + render Shipwright::IconComponent.new(name: :info) + end + + # @label All Icons + def gallery + render_with_template + end + + # @label Sizes + def sizes + render_with_template + end + + # @label Colored (via text-*) + def colored + render_with_template + end + + # @label With aria-label (non-decorative) + def with_aria_label + render Shipwright::IconComponent.new(name: :info, "aria-label": "More information") + end + end +end diff --git a/test/components/previews/shipwright/icon_component_preview/colored.html.erb b/test/components/previews/shipwright/icon_component_preview/colored.html.erb new file mode 100644 index 0000000..b76910f --- /dev/null +++ b/test/components/previews/shipwright/icon_component_preview/colored.html.erb @@ -0,0 +1,6 @@ +
+ <%= render Shipwright::IconComponent.new(name: :"alert-circle", class: "text-interactive-primary-default") %> + <%= render Shipwright::IconComponent.new(name: :"alert-triangle", class: "text-utility-negative-default") %> + <%= render Shipwright::IconComponent.new(name: :"check-circle", class: "text-interactive-primary-default") %> + <%= render Shipwright::IconComponent.new(name: :info, class: "text-interactive-primary-hover") %> +
diff --git a/test/components/previews/shipwright/icon_component_preview/gallery.html.erb b/test/components/previews/shipwright/icon_component_preview/gallery.html.erb new file mode 100644 index 0000000..d754621 --- /dev/null +++ b/test/components/previews/shipwright/icon_component_preview/gallery.html.erb @@ -0,0 +1,8 @@ +
+ <% Shipwright::IconComponent.icon_names.each do |name| %> +
+ <%= render Shipwright::IconComponent.new(name: name) %> + <%= name %> +
+ <% end %> +
diff --git a/test/components/previews/shipwright/icon_component_preview/sizes.html.erb b/test/components/previews/shipwright/icon_component_preview/sizes.html.erb new file mode 100644 index 0000000..2f351a9 --- /dev/null +++ b/test/components/previews/shipwright/icon_component_preview/sizes.html.erb @@ -0,0 +1,5 @@ +
+ <%= render Shipwright::IconComponent.new(name: :info, size: :sm) %> + <%= render Shipwright::IconComponent.new(name: :info, size: :md) %> + <%= render Shipwright::IconComponent.new(name: :info, size: :lg) %> +
diff --git a/test/components/shipwright/icon_component_test.rb b/test/components/shipwright/icon_component_test.rb new file mode 100644 index 0000000..996d1c9 --- /dev/null +++ b/test/components/shipwright/icon_component_test.rb @@ -0,0 +1,84 @@ +require "test_helper" + +class Shipwright::IconComponentTest < ViewComponent::TestCase + def test_renders_known_icon_by_name + render_inline(Shipwright::IconComponent.new(name: :info)) + + assert_selector "svg" + assert_selector "svg circle[cx='12'][cy='12'][r='10']" + end + + def test_uses_current_color_for_stroke + render_inline(Shipwright::IconComponent.new(name: :check)) + + assert_selector "svg[stroke='currentColor']" + end + + def test_default_size_is_medium_24 + render_inline(Shipwright::IconComponent.new(name: :info)) + + assert_selector "svg.w-6.h-6" + end + + def test_small_size_16 + render_inline(Shipwright::IconComponent.new(name: :info, size: :sm)) + + assert_selector "svg.w-4.h-4" + end + + def test_large_size_32 + render_inline(Shipwright::IconComponent.new(name: :info, size: :lg)) + + assert_selector "svg.w-8.h-8" + end + + def test_accepts_string_or_symbol_name + render_inline(Shipwright::IconComponent.new(name: "info")) + assert_selector "svg" + end + + def test_consumer_class_merges + render_inline(Shipwright::IconComponent.new(name: :info, class: "text-utility-negative-default")) + + assert_selector "svg.text-utility-negative-default" + end + + def test_passes_html_attributes + render_inline(Shipwright::IconComponent.new(name: :info, id: "info-icon", data: { action: "tooltip" })) + + assert_selector "svg#info-icon[data-action='tooltip']" + end + + def test_aria_hidden_by_default + render_inline(Shipwright::IconComponent.new(name: :info)) + + assert_selector "svg[aria-hidden='true']" + end + + def test_aria_label_makes_icon_visible_to_assistive_tech + render_inline(Shipwright::IconComponent.new(name: :info, "aria-label": "More information")) + + assert_selector "svg[aria-label='More information']" + # When aria-label is explicitly set, the icon should not be aria-hidden + assert_no_selector "svg[aria-hidden='true']" + end + + def test_raises_on_unknown_icon + assert_raises(ArgumentError) do + render_inline(Shipwright::IconComponent.new(name: :nonexistent)) + end + end + + def test_raises_on_invalid_size + assert_raises(ArgumentError) do + render_inline(Shipwright::IconComponent.new(name: :info, size: :xl)) + end + end + + def test_exposes_available_icon_names + assert_kind_of Array, Shipwright::IconComponent.icon_names + assert_includes Shipwright::IconComponent.icon_names, :info + assert_includes Shipwright::IconComponent.icon_names, :check + assert_includes Shipwright::IconComponent.icon_names, :"chevron-down" + end +end diff --git a/test/dummy/app/assets/builds/tailwind.css b/test/dummy/app/assets/builds/tailwind.css index ca8b6d6..120a09b 100644 --- a/test/dummy/app/assets/builds/tailwind.css +++ b/test/dummy/app/assets/builds/tailwind.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:"Manrope", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-white:#fff;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-normal:400;--font-weight-medium:500;--radius-md:6px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-interactive-primary-default:#14161c;--color-interactive-primary-hover:#595d6a;--color-interactive-primary-pressed:#333747;--color-interactive-secondary-default:#fdfdfd;--color-interactive-disable:#eaeaea;--color-border-interactive:#14161c;--color-utility-negative-default:#b62e2e;--color-text-primary:#050507;--color-text-inverse:#fdfdfd;--color-text-disabled:#595d6a;--color-background-primary:#fdfdfd;--leading-2xs:1.25rem;--spacing-2xs:8px;--spacing-sm:16px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.inline-flex{display:inline-flex}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-13{height:calc(var(--spacing) * 13)}.w-8{width:calc(var(--spacing) * 8)}.w-11{width:calc(var(--spacing) * 11)}.w-13{width:calc(var(--spacing) * 13)}.w-full{width:100%}.cursor-not-allowed{cursor:not-allowed}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2xs{gap:var(--spacing-2xs)}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-border-interactive{border-color:var(--color-border-interactive)}.bg-interactive-disable{background-color:var(--color-interactive-disable)}.bg-interactive-primary-default{background-color:var(--color-interactive-primary-default)}.bg-interactive-secondary-default{background-color:var(--color-interactive-secondary-default)}.bg-transparent{background-color:#0000}.bg-utility-negative-default{background-color:var(--color-utility-negative-default)}.p-0{padding:calc(var(--spacing) * 0)}.px-sm{padding-inline:var(--spacing-sm)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-2xs{--tw-leading:var(--leading-2xs);line-height:var(--leading-2xs)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-text-disabled{color:var(--color-text-disabled)}.text-text-inverse{color:var(--color-text-inverse)}.text-text-primary{color:var(--color-text-primary)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:bg-interactive-disable:hover{background-color:var(--color-interactive-disable)}.hover\:bg-interactive-primary-hover:hover{background-color:var(--color-interactive-primary-hover)}.hover\:bg-utility-negative-default\/90:hover{background-color:#b62e2ee6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-utility-negative-default\/90:hover{background-color:color-mix(in oklab, var(--color-utility-negative-default) 90%, transparent)}}.hover\:text-text-disabled:hover{color:var(--color-text-disabled)}.hover\:text-text-inverse:hover{color:var(--color-text-inverse)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-interactive-primary-default:focus-visible{--tw-ring-color:var(--color-interactive-primary-default)}.focus-visible\:ring-utility-negative-default:focus-visible{--tw-ring-color:var(--color-utility-negative-default)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background-primary:focus-visible{--tw-ring-offset-color:var(--color-background-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:bg-interactive-disable:active{background-color:var(--color-interactive-disable)}.active\:bg-interactive-primary-pressed:active{background-color:var(--color-interactive-primary-pressed)}.active\:bg-utility-negative-default\/80:active{background-color:#b62e2ecc}@supports (color:color-mix(in lab, red, red)){.active\:bg-utility-negative-default\/80:active{background-color:color-mix(in oklab, var(--color-utility-negative-default) 80%, transparent)}}.active\:text-text-inverse:active{color:var(--color-text-inverse)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:"Manrope", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-normal:400;--radius-md:6px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-interactive-primary-default:#14161c;--color-interactive-primary-hover:#595d6a;--color-interactive-primary-pressed:#333747;--color-interactive-secondary-default:#fdfdfd;--color-interactive-disable:#eaeaea;--color-border-interactive:#14161c;--color-utility-negative-default:#b62e2e;--color-text-primary:#050507;--color-text-inverse:#fdfdfd;--color-text-disabled:#595d6a;--color-background-primary:#fdfdfd;--leading-2xs:1.25rem;--spacing-2xs:8px;--spacing-sm:16px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.inline-flex{display:inline-flex}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-13{height:calc(var(--spacing) * 13)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-11{width:calc(var(--spacing) * 11)}.w-13{width:calc(var(--spacing) * 13)}.w-full{width:100%}.cursor-not-allowed{cursor:not-allowed}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2xs{gap:var(--spacing-2xs)}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-border-interactive{border-color:var(--color-border-interactive)}.bg-interactive-disable{background-color:var(--color-interactive-disable)}.bg-interactive-primary-default{background-color:var(--color-interactive-primary-default)}.bg-interactive-secondary-default{background-color:var(--color-interactive-secondary-default)}.bg-transparent{background-color:#0000}.bg-utility-negative-default{background-color:var(--color-utility-negative-default)}.p-0{padding:calc(var(--spacing) * 0)}.px-sm{padding-inline:var(--spacing-sm)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-2xs{--tw-leading:var(--leading-2xs);line-height:var(--leading-2xs)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-text-disabled{color:var(--color-text-disabled)}.text-text-inverse{color:var(--color-text-inverse)}.text-text-primary{color:var(--color-text-primary)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:bg-interactive-disable:hover{background-color:var(--color-interactive-disable)}.hover\:bg-interactive-primary-hover:hover{background-color:var(--color-interactive-primary-hover)}.hover\:bg-utility-negative-default\/90:hover{background-color:#b62e2ee6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-utility-negative-default\/90:hover{background-color:color-mix(in oklab, var(--color-utility-negative-default) 90%, transparent)}}.hover\:text-text-disabled:hover{color:var(--color-text-disabled)}.hover\:text-text-inverse:hover{color:var(--color-text-inverse)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-interactive-primary-default:focus-visible{--tw-ring-color:var(--color-interactive-primary-default)}.focus-visible\:ring-utility-negative-default:focus-visible{--tw-ring-color:var(--color-utility-negative-default)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background-primary:focus-visible{--tw-ring-offset-color:var(--color-background-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:bg-interactive-disable:active{background-color:var(--color-interactive-disable)}.active\:bg-interactive-primary-pressed:active{background-color:var(--color-interactive-primary-pressed)}.active\:bg-utility-negative-default\/80:active{background-color:#b62e2ecc}@supports (color:color-mix(in lab, red, red)){.active\:bg-utility-negative-default\/80:active{background-color:color-mix(in oklab, var(--color-utility-negative-default) 80%, transparent)}}.active\:text-text-inverse:active{color:var(--color-text-inverse)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000} \ No newline at end of file diff --git a/test/dummy/app/views/pages/components.html.erb b/test/dummy/app/views/pages/components.html.erb index de3e4fc..2ae52ca 100644 --- a/test/dummy/app/views/pages/components.html.erb +++ b/test/dummy/app/views/pages/components.html.erb @@ -49,4 +49,21 @@
<%= render Shipwright::ButtonComponent.new(class: "w-full") do %>Full Width Override<% end %>
+ +

Icons

+
+ <% Shipwright::IconComponent.icon_names.first(10).each do |name| %> +
+ <%= render Shipwright::IconComponent.new(name: name) %> + <%= name %> +
+ <% end %> +
+ +

Icon — Sizes

+
+ <%= render Shipwright::IconComponent.new(name: :info, size: :sm) %> + <%= render Shipwright::IconComponent.new(name: :info, size: :md) %> + <%= render Shipwright::IconComponent.new(name: :info, size: :lg) %> +
From 63161a87229e62c687e251a2eef77b73b1ec8030 Mon Sep 17 00:00:00 2001 From: Jordan Burke Date: Sat, 18 Apr 2026 16:48:19 -0400 Subject: [PATCH 2/5] feat(icons): expand to full Feather set (289 icons) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generated from feather-icons@4.29.2 icons.json plus two Shipwright additions to match Figma's icon page (node 2:163): - code-horizontal: alias for Feather's 'code' (matches Shipwright Pro's naming convention) - star-filled: filled variant using the star polygon with fill=currentColor Previously only 21 starter icons were shipped. This adds the remaining ~270 to cover every icon on the Shipwright Pro 'Icons / General' page. Brand icons (payment methods at node 2:824 and social icons at 2:940) are multi-color composite SVGs served from Figma's temporary CDN and need a different rendering approach — they'll ship in a follow-up BrandIconComponent PR. Gallery preview updated to render all icons in an 8-column grid. Showcase page shows a curated sample plus pointer to Lookbook for the full set. All 38 existing tests pass (Icon manifest expansion preserves the starter icons we already shipped). --- app/components/shipwright/icons.rb | 319 ++++++++++++++++-- .../icon_component_preview/gallery.html.erb | 20 +- test/dummy/app/assets/builds/tailwind.css | 2 +- .../dummy/app/views/pages/components.html.erb | 7 +- 4 files changed, 314 insertions(+), 34 deletions(-) diff --git a/app/components/shipwright/icons.rb b/app/components/shipwright/icons.rb index 1667596..047a160 100644 --- a/app/components/shipwright/icons.rb +++ b/app/components/shipwright/icons.rb @@ -3,30 +3,301 @@ module Shipwright # stroke-based design. Values are the inner SVG fragment; the wrapping # with viewBox/stroke attributes is provided by IconComponent. # - # To add an icon: copy its inner content from https://feathericons.com/ - # and paste it below. Keep icon names in kebab-case matching Figma's - # icon name in the design file. + # Full set (287 icons) plus Shipwright additions: + # - code-horizontal: alias for Feather's `code` (matches Figma naming) + # - star-filled: filled variant using the star polygon with fill=currentColor + # + # To add a brand icon (payment/social), use BrandIconComponent instead — + # those are multi-color SVGs that need different rendering. ICONS = { - "info" => %().html_safe, - "alert-circle" => %().html_safe, - "alert-triangle" => %().html_safe, - "check" => %().html_safe, - "check-circle" => %().html_safe, - "x" => %().html_safe, - "x-circle" => %().html_safe, - "chevron-down" => %().html_safe, - "chevron-up" => %().html_safe, - "chevron-left" => %().html_safe, - "chevron-right" => %().html_safe, - "plus" => %().html_safe, - "minus" => %().html_safe, - "search" => %().html_safe, - "menu" => %().html_safe, - "external-link" => %().html_safe, - "settings" => %().html_safe, - "arrow-right" => %().html_safe, - "arrow-left" => %().html_safe, - "help-circle" => %().html_safe, - "trash" => %().html_safe + "activity" => %().html_safe, + "airplay" => %().html_safe, + "alert-circle" => %().html_safe, + "alert-octagon" => %().html_safe, + "alert-triangle" => %().html_safe, + "align-center" => %().html_safe, + "align-justify" => %().html_safe, + "align-left" => %().html_safe, + "align-right" => %().html_safe, + "anchor" => %().html_safe, + "aperture" => %().html_safe, + "archive" => %().html_safe, + "arrow-down" => %().html_safe, + "arrow-down-circle" => %().html_safe, + "arrow-down-left" => %().html_safe, + "arrow-down-right" => %().html_safe, + "arrow-left" => %().html_safe, + "arrow-left-circle" => %().html_safe, + "arrow-right" => %().html_safe, + "arrow-right-circle" => %().html_safe, + "arrow-up" => %().html_safe, + "arrow-up-circle" => %().html_safe, + "arrow-up-left" => %().html_safe, + "arrow-up-right" => %().html_safe, + "at-sign" => %().html_safe, + "award" => %().html_safe, + "bar-chart" => %().html_safe, + "bar-chart-2" => %().html_safe, + "battery" => %().html_safe, + "battery-charging" => %().html_safe, + "bell" => %().html_safe, + "bell-off" => %().html_safe, + "bluetooth" => %().html_safe, + "bold" => %().html_safe, + "book" => %().html_safe, + "book-open" => %().html_safe, + "bookmark" => %().html_safe, + "box" => %().html_safe, + "briefcase" => %().html_safe, + "calendar" => %().html_safe, + "camera" => %().html_safe, + "camera-off" => %().html_safe, + "cast" => %().html_safe, + "check" => %().html_safe, + "check-circle" => %().html_safe, + "check-square" => %().html_safe, + "chevron-down" => %().html_safe, + "chevron-left" => %().html_safe, + "chevron-right" => %().html_safe, + "chevron-up" => %().html_safe, + "chevrons-down" => %().html_safe, + "chevrons-left" => %().html_safe, + "chevrons-right" => %().html_safe, + "chevrons-up" => %().html_safe, + "chrome" => %().html_safe, + "circle" => %().html_safe, + "clipboard" => %().html_safe, + "clock" => %().html_safe, + "cloud" => %().html_safe, + "cloud-drizzle" => %().html_safe, + "cloud-lightning" => %().html_safe, + "cloud-off" => %().html_safe, + "cloud-rain" => %().html_safe, + "cloud-snow" => %().html_safe, + "code" => %().html_safe, + "code-horizontal" => %().html_safe, + "codepen" => %().html_safe, + "codesandbox" => %().html_safe, + "coffee" => %().html_safe, + "columns" => %().html_safe, + "command" => %().html_safe, + "compass" => %().html_safe, + "copy" => %().html_safe, + "corner-down-left" => %().html_safe, + "corner-down-right" => %().html_safe, + "corner-left-down" => %().html_safe, + "corner-left-up" => %().html_safe, + "corner-right-down" => %().html_safe, + "corner-right-up" => %().html_safe, + "corner-up-left" => %().html_safe, + "corner-up-right" => %().html_safe, + "cpu" => %().html_safe, + "credit-card" => %().html_safe, + "crop" => %().html_safe, + "crosshair" => %().html_safe, + "database" => %().html_safe, + "delete" => %().html_safe, + "disc" => %().html_safe, + "divide" => %().html_safe, + "divide-circle" => %().html_safe, + "divide-square" => %().html_safe, + "dollar-sign" => %().html_safe, + "download" => %().html_safe, + "download-cloud" => %().html_safe, + "dribbble" => %().html_safe, + "droplet" => %().html_safe, + "edit" => %().html_safe, + "edit-2" => %().html_safe, + "edit-3" => %().html_safe, + "external-link" => %().html_safe, + "eye" => %().html_safe, + "eye-off" => %().html_safe, + "facebook" => %().html_safe, + "fast-forward" => %().html_safe, + "feather" => %().html_safe, + "figma" => %().html_safe, + "file" => %().html_safe, + "file-minus" => %().html_safe, + "file-plus" => %().html_safe, + "file-text" => %().html_safe, + "film" => %().html_safe, + "filter" => %().html_safe, + "flag" => %().html_safe, + "folder" => %().html_safe, + "folder-minus" => %().html_safe, + "folder-plus" => %().html_safe, + "framer" => %().html_safe, + "frown" => %().html_safe, + "gift" => %().html_safe, + "git-branch" => %().html_safe, + "git-commit" => %().html_safe, + "git-merge" => %().html_safe, + "git-pull-request" => %().html_safe, + "github" => %().html_safe, + "gitlab" => %().html_safe, + "globe" => %().html_safe, + "grid" => %().html_safe, + "hard-drive" => %().html_safe, + "hash" => %().html_safe, + "headphones" => %().html_safe, + "heart" => %().html_safe, + "help-circle" => %().html_safe, + "hexagon" => %().html_safe, + "home" => %().html_safe, + "image" => %().html_safe, + "inbox" => %().html_safe, + "info" => %().html_safe, + "instagram" => %().html_safe, + "italic" => %().html_safe, + "key" => %().html_safe, + "layers" => %().html_safe, + "layout" => %().html_safe, + "life-buoy" => %().html_safe, + "link" => %().html_safe, + "link-2" => %().html_safe, + "linkedin" => %().html_safe, + "list" => %().html_safe, + "loader" => %().html_safe, + "lock" => %().html_safe, + "log-in" => %().html_safe, + "log-out" => %().html_safe, + "mail" => %().html_safe, + "map" => %().html_safe, + "map-pin" => %().html_safe, + "maximize" => %().html_safe, + "maximize-2" => %().html_safe, + "meh" => %().html_safe, + "menu" => %().html_safe, + "message-circle" => %().html_safe, + "message-square" => %().html_safe, + "mic" => %().html_safe, + "mic-off" => %().html_safe, + "minimize" => %().html_safe, + "minimize-2" => %().html_safe, + "minus" => %().html_safe, + "minus-circle" => %().html_safe, + "minus-square" => %().html_safe, + "monitor" => %().html_safe, + "moon" => %().html_safe, + "more-horizontal" => %().html_safe, + "more-vertical" => %().html_safe, + "mouse-pointer" => %().html_safe, + "move" => %().html_safe, + "music" => %().html_safe, + "navigation" => %().html_safe, + "navigation-2" => %().html_safe, + "octagon" => %().html_safe, + "package" => %().html_safe, + "paperclip" => %().html_safe, + "pause" => %().html_safe, + "pause-circle" => %().html_safe, + "pen-tool" => %().html_safe, + "percent" => %().html_safe, + "phone" => %().html_safe, + "phone-call" => %().html_safe, + "phone-forwarded" => %().html_safe, + "phone-incoming" => %().html_safe, + "phone-missed" => %().html_safe, + "phone-off" => %().html_safe, + "phone-outgoing" => %().html_safe, + "pie-chart" => %().html_safe, + "play" => %().html_safe, + "play-circle" => %().html_safe, + "plus" => %().html_safe, + "plus-circle" => %().html_safe, + "plus-square" => %().html_safe, + "pocket" => %().html_safe, + "power" => %().html_safe, + "printer" => %().html_safe, + "radio" => %().html_safe, + "refresh-ccw" => %().html_safe, + "refresh-cw" => %().html_safe, + "repeat" => %().html_safe, + "rewind" => %().html_safe, + "rotate-ccw" => %().html_safe, + "rotate-cw" => %().html_safe, + "rss" => %().html_safe, + "save" => %().html_safe, + "scissors" => %().html_safe, + "search" => %().html_safe, + "send" => %().html_safe, + "server" => %().html_safe, + "settings" => %().html_safe, + "share" => %().html_safe, + "share-2" => %().html_safe, + "shield" => %().html_safe, + "shield-off" => %().html_safe, + "shopping-bag" => %().html_safe, + "shopping-cart" => %().html_safe, + "shuffle" => %().html_safe, + "sidebar" => %().html_safe, + "skip-back" => %().html_safe, + "skip-forward" => %().html_safe, + "slack" => %().html_safe, + "slash" => %().html_safe, + "sliders" => %().html_safe, + "smartphone" => %().html_safe, + "smile" => %().html_safe, + "speaker" => %().html_safe, + "square" => %().html_safe, + "star" => %().html_safe, + "star-filled" => %().html_safe, + "stop-circle" => %().html_safe, + "sun" => %().html_safe, + "sunrise" => %().html_safe, + "sunset" => %().html_safe, + "table" => %().html_safe, + "tablet" => %().html_safe, + "tag" => %().html_safe, + "target" => %().html_safe, + "terminal" => %().html_safe, + "thermometer" => %().html_safe, + "thumbs-down" => %().html_safe, + "thumbs-up" => %().html_safe, + "toggle-left" => %().html_safe, + "toggle-right" => %().html_safe, + "tool" => %().html_safe, + "trash" => %().html_safe, + "trash-2" => %().html_safe, + "trello" => %().html_safe, + "trending-down" => %().html_safe, + "trending-up" => %().html_safe, + "triangle" => %().html_safe, + "truck" => %().html_safe, + "tv" => %().html_safe, + "twitch" => %().html_safe, + "twitter" => %().html_safe, + "type" => %().html_safe, + "umbrella" => %().html_safe, + "underline" => %().html_safe, + "unlock" => %().html_safe, + "upload" => %().html_safe, + "upload-cloud" => %().html_safe, + "user" => %().html_safe, + "user-check" => %().html_safe, + "user-minus" => %().html_safe, + "user-plus" => %().html_safe, + "user-x" => %().html_safe, + "users" => %().html_safe, + "video" => %().html_safe, + "video-off" => %().html_safe, + "voicemail" => %().html_safe, + "volume" => %().html_safe, + "volume-1" => %().html_safe, + "volume-2" => %().html_safe, + "volume-x" => %().html_safe, + "watch" => %().html_safe, + "wifi" => %().html_safe, + "wifi-off" => %().html_safe, + "wind" => %().html_safe, + "x" => %().html_safe, + "x-circle" => %().html_safe, + "x-octagon" => %().html_safe, + "x-square" => %().html_safe, + "youtube" => %().html_safe, + "zap" => %().html_safe, + "zap-off" => %().html_safe, + "zoom-in" => %().html_safe, + "zoom-out" => %().html_safe, }.freeze end diff --git a/test/components/previews/shipwright/icon_component_preview/gallery.html.erb b/test/components/previews/shipwright/icon_component_preview/gallery.html.erb index d754621..daefa2b 100644 --- a/test/components/previews/shipwright/icon_component_preview/gallery.html.erb +++ b/test/components/previews/shipwright/icon_component_preview/gallery.html.erb @@ -1,8 +1,14 @@ -
- <% Shipwright::IconComponent.icon_names.each do |name| %> -
- <%= render Shipwright::IconComponent.new(name: name) %> - <%= name %> -
- <% end %> +
+

+ <%= Shipwright::IconComponent.icon_names.size %> icons available. Feather Icons (MIT) + plus Shipwright additions (code-horizontal, star-filled). +

+
+ <% Shipwright::IconComponent.icon_names.each do |name| %> +
+ <%= render Shipwright::IconComponent.new(name: name) %> + <%= name %> +
+ <% end %> +
diff --git a/test/dummy/app/assets/builds/tailwind.css b/test/dummy/app/assets/builds/tailwind.css index 120a09b..51f3d63 100644 --- a/test/dummy/app/assets/builds/tailwind.css +++ b/test/dummy/app/assets/builds/tailwind.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:"Manrope", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-normal:400;--radius-md:6px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-interactive-primary-default:#14161c;--color-interactive-primary-hover:#595d6a;--color-interactive-primary-pressed:#333747;--color-interactive-secondary-default:#fdfdfd;--color-interactive-disable:#eaeaea;--color-border-interactive:#14161c;--color-utility-negative-default:#b62e2e;--color-text-primary:#050507;--color-text-inverse:#fdfdfd;--color-text-disabled:#595d6a;--color-background-primary:#fdfdfd;--leading-2xs:1.25rem;--spacing-2xs:8px;--spacing-sm:16px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.inline-flex{display:inline-flex}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-13{height:calc(var(--spacing) * 13)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-11{width:calc(var(--spacing) * 11)}.w-13{width:calc(var(--spacing) * 13)}.w-full{width:100%}.cursor-not-allowed{cursor:not-allowed}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2xs{gap:var(--spacing-2xs)}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-border-interactive{border-color:var(--color-border-interactive)}.bg-interactive-disable{background-color:var(--color-interactive-disable)}.bg-interactive-primary-default{background-color:var(--color-interactive-primary-default)}.bg-interactive-secondary-default{background-color:var(--color-interactive-secondary-default)}.bg-transparent{background-color:#0000}.bg-utility-negative-default{background-color:var(--color-utility-negative-default)}.p-0{padding:calc(var(--spacing) * 0)}.px-sm{padding-inline:var(--spacing-sm)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-2xs{--tw-leading:var(--leading-2xs);line-height:var(--leading-2xs)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-text-disabled{color:var(--color-text-disabled)}.text-text-inverse{color:var(--color-text-inverse)}.text-text-primary{color:var(--color-text-primary)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:bg-interactive-disable:hover{background-color:var(--color-interactive-disable)}.hover\:bg-interactive-primary-hover:hover{background-color:var(--color-interactive-primary-hover)}.hover\:bg-utility-negative-default\/90:hover{background-color:#b62e2ee6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-utility-negative-default\/90:hover{background-color:color-mix(in oklab, var(--color-utility-negative-default) 90%, transparent)}}.hover\:text-text-disabled:hover{color:var(--color-text-disabled)}.hover\:text-text-inverse:hover{color:var(--color-text-inverse)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-interactive-primary-default:focus-visible{--tw-ring-color:var(--color-interactive-primary-default)}.focus-visible\:ring-utility-negative-default:focus-visible{--tw-ring-color:var(--color-utility-negative-default)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background-primary:focus-visible{--tw-ring-offset-color:var(--color-background-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:bg-interactive-disable:active{background-color:var(--color-interactive-disable)}.active\:bg-interactive-primary-pressed:active{background-color:var(--color-interactive-primary-pressed)}.active\:bg-utility-negative-default\/80:active{background-color:#b62e2ecc}@supports (color:color-mix(in lab, red, red)){.active\:bg-utility-negative-default\/80:active{background-color:color-mix(in oklab, var(--color-utility-negative-default) 80%, transparent)}}.active\:text-text-inverse:active{color:var(--color-text-inverse)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:"Manrope", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-normal:400;--radius-md:6px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-interactive-primary-default:#14161c;--color-interactive-primary-hover:#595d6a;--color-interactive-primary-pressed:#333747;--color-interactive-secondary-default:#fdfdfd;--color-interactive-disable:#eaeaea;--color-border-interactive:#14161c;--color-utility-negative-default:#b62e2e;--color-text-primary:#050507;--color-text-inverse:#fdfdfd;--color-text-disabled:#595d6a;--color-background-primary:#fdfdfd;--leading-2xs:1.25rem;--spacing-2xs:8px;--spacing-sm:16px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.grid{display:grid}.inline-flex{display:inline-flex}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-13{height:calc(var(--spacing) * 13)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-11{width:calc(var(--spacing) * 11)}.w-13{width:calc(var(--spacing) * 13)}.w-full{width:100%}.cursor-not-allowed{cursor:not-allowed}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2xs{gap:var(--spacing-2xs)}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-border-interactive{border-color:var(--color-border-interactive)}.bg-interactive-disable{background-color:var(--color-interactive-disable)}.bg-interactive-primary-default{background-color:var(--color-interactive-primary-default)}.bg-interactive-secondary-default{background-color:var(--color-interactive-secondary-default)}.bg-transparent{background-color:#0000}.bg-utility-negative-default{background-color:var(--color-utility-negative-default)}.p-0{padding:calc(var(--spacing) * 0)}.px-sm{padding-inline:var(--spacing-sm)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-2xs{--tw-leading:var(--leading-2xs);line-height:var(--leading-2xs)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-text-disabled{color:var(--color-text-disabled)}.text-text-inverse{color:var(--color-text-inverse)}.text-text-primary{color:var(--color-text-primary)}.italic{font-style:italic}.underline{text-decoration-line:underline}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:bg-interactive-disable:hover{background-color:var(--color-interactive-disable)}.hover\:bg-interactive-primary-hover:hover{background-color:var(--color-interactive-primary-hover)}.hover\:bg-utility-negative-default\/90:hover{background-color:#b62e2ee6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-utility-negative-default\/90:hover{background-color:color-mix(in oklab, var(--color-utility-negative-default) 90%, transparent)}}.hover\:text-text-disabled:hover{color:var(--color-text-disabled)}.hover\:text-text-inverse:hover{color:var(--color-text-inverse)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-interactive-primary-default:focus-visible{--tw-ring-color:var(--color-interactive-primary-default)}.focus-visible\:ring-utility-negative-default:focus-visible{--tw-ring-color:var(--color-utility-negative-default)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background-primary:focus-visible{--tw-ring-offset-color:var(--color-background-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:bg-interactive-disable:active{background-color:var(--color-interactive-disable)}.active\:bg-interactive-primary-pressed:active{background-color:var(--color-interactive-primary-pressed)}.active\:bg-utility-negative-default\/80:active{background-color:#b62e2ecc}@supports (color:color-mix(in lab, red, red)){.active\:bg-utility-negative-default\/80:active{background-color:color-mix(in oklab, var(--color-utility-negative-default) 80%, transparent)}}.active\:text-text-inverse:active{color:var(--color-text-inverse)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} \ No newline at end of file diff --git a/test/dummy/app/views/pages/components.html.erb b/test/dummy/app/views/pages/components.html.erb index 2ae52ca..1a6be39 100644 --- a/test/dummy/app/views/pages/components.html.erb +++ b/test/dummy/app/views/pages/components.html.erb @@ -50,9 +50,12 @@ <%= render Shipwright::ButtonComponent.new(class: "w-full") do %>Full Width Override<% end %>
-

Icons

+

Icons — sample (full gallery in Lookbook)

+

+ <%= Shipwright::IconComponent.icon_names.size %> icons from Feather Icons (MIT). +

- <% Shipwright::IconComponent.icon_names.first(10).each do |name| %> + <% [:info, :"alert-circle", :"alert-triangle", :check, :x, :"chevron-down", :plus, :search, :menu, :settings, :heart, :star, :"star-filled"].each do |name| %>
<%= render Shipwright::IconComponent.new(name: name) %> <%= name %> From 6838929b14933e7178f56ec8a260fa67db206f6e Mon Sep 17 00:00:00 2001 From: Jordan Burke Date: Sat, 18 Apr 2026 16:57:05 -0400 Subject: [PATCH 3/5] feat: add BrandIconComponent with Figma SVG exporter Brand icons (payment methods + social platforms) from Shipwright Pro's Figma are multi-color composite SVGs that need different rendering than Feather's monochrome stroke icons. Added as a parallel component: <%= render Shipwright::BrandIconComponent.new(name: :visa-color) %> Architecture: - SVGs live in app/assets/images/shipwright/brand/ (one file per icon) - Component loads and memoizes the directory on first access - Renders SVG inline (preserves brand colors, allows CSS styling) - Strips intrinsic width/height, applies h-* class + w-auto so non-square icons (e.g., Visa) keep their aspect ratio - Raises a helpful ArgumentError directing users to the exporter if they reference a missing icon New files: - script/export_brand_icons.rb: downloads all 59 brand SVGs from Shipwright Pro Figma via the Figma REST API. Maps every node ID from the Figma Icons/Payment Method and Icons/Social pages to a normalized kebab-case filename. Requires FIGMA_ACCESS_TOKEN env var. - app/components/shipwright/brand_icon_component.rb: the component. - test fixtures at test/fixtures/brand_icons/ so tests don't need Figma access. BrandIconComponent.asset_path is swappable per-test. - 4 Lookbook previews including an empty_state scenario explaining how to run the exporter. 14 new component tests pass. Total suite: 52 tests, 111 assertions. Usage: FIGMA_ACCESS_TOKEN=xxx ruby script/export_brand_icons.rb # Review, commit the SVGs. 59 icons covering: # Payment: amex, visa, mastercard, applepay, paypal, discover, # cash, cash-dollar, card-default (color/fill/outline) # Social: facebook, instagram, youtube, google, linkedin, apple, # snapchat, pinterest, medium, angelist, slack, dribbble, # figma, discord, clubhouse, tumblr, telegram, tiktok, # vk, signal, reddit, github, fb-messenger, skype, # spectrum, zoom, facetime, google-meet, behance, # invision, microsoft --- .../shipwright/brand_icon_component.rb | 113 ++++++++++++ script/export_brand_icons.rb | 161 ++++++++++++++++++ .../brand_icon_component_preview.rb | 23 +++ .../empty_state.html.erb | 13 ++ .../gallery.html.erb | 20 +++ .../rounded.html.erb | 9 + .../sizes.html.erb | 10 ++ .../shipwright/brand_icon_component_test.rb | 108 ++++++++++++ test/dummy/app/assets/builds/tailwind.css | 2 +- test/fixtures/brand_icons/placeholder.svg | 1 + test/fixtures/brand_icons/wide-brand.svg | 1 + 11 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 app/components/shipwright/brand_icon_component.rb create mode 100755 script/export_brand_icons.rb create mode 100644 test/components/previews/shipwright/brand_icon_component_preview.rb create mode 100644 test/components/previews/shipwright/brand_icon_component_preview/empty_state.html.erb create mode 100644 test/components/previews/shipwright/brand_icon_component_preview/gallery.html.erb create mode 100644 test/components/previews/shipwright/brand_icon_component_preview/rounded.html.erb create mode 100644 test/components/previews/shipwright/brand_icon_component_preview/sizes.html.erb create mode 100644 test/components/shipwright/brand_icon_component_test.rb create mode 100644 test/fixtures/brand_icons/placeholder.svg create mode 100644 test/fixtures/brand_icons/wide-brand.svg diff --git a/app/components/shipwright/brand_icon_component.rb b/app/components/shipwright/brand_icon_component.rb new file mode 100644 index 0000000..02cb287 --- /dev/null +++ b/app/components/shipwright/brand_icon_component.rb @@ -0,0 +1,113 @@ +module Shipwright + # Renders a multi-color brand SVG (payment method, social platform, etc.) + # exported from Shipwright Pro's Figma source. + # + # Brand icons differ from Feather icons (rendered by IconComponent) in + # that they preserve the brand's own colors rather than inheriting via + # currentColor. The component loads SVGs from a directory, strips their + # intrinsic width/height, and applies a Tailwind height class — width + # auto-scales so non-square icons (e.g., Visa) keep their aspect ratio. + # + # Populate the SVG directory by running: + # + # FIGMA_ACCESS_TOKEN=xxx ruby script/export_brand_icons.rb + # + # Available names are discoverable via `BrandIconComponent.icon_names`. + class BrandIconComponent < BaseComponent + # Default SVG directory — overridable for tests via the asset_path class setting. + DEFAULT_ASSET_PATH = Pathname.new(__dir__).join("../../assets/images/shipwright/brand").expand_path.freeze + + SIZES = { + sm: "h-4", # 16px + md: "h-6", # 24px + lg: "h-8" # 32px + }.freeze + + class << self + attr_writer :asset_path + + def asset_path + @asset_path ||= DEFAULT_ASSET_PATH + end + + # Scan the asset directory and return a hash of name => inline SVG string. + # Memoized per-asset-path so tests can swap paths cleanly. + def icons + @icons ||= {} + @icons[asset_path.to_s] ||= begin + pattern = asset_path.join("*.svg") + Dir.glob(pattern).each_with_object({}) do |file, hash| + name = File.basename(file, ".svg") + hash[name] = File.read(file) + end.freeze + end + end + + def icon_names + icons.keys.map(&:to_sym).sort.freeze + end + + # Allow tests to reset the memoization. + def reset_cache! + @icons = nil + end + end + + # `class:` is a Ruby reserved word used as a keyword arg — requires + # binding.local_variable_get. Shipwright convention. + def initialize(name:, size: :md, class: nil, **html_attrs) + @name = name.to_s + @size = size + @class = binding.local_variable_get(:class) + @html_attrs = html_attrs + end + + def call + raw_svg = self.class.icons.fetch(@name) do + available = self.class.icon_names + hint = if available.empty? + " No brand icons loaded — run `ruby script/export_brand_icons.rb` with a FIGMA_ACCESS_TOKEN to populate app/assets/images/shipwright/brand/." + else + " Available: #{available.join(', ')}" + end + raise ArgumentError, "#{self.class}: unknown brand icon :#{@name}.#{hint}" + end + + size_class = SIZES.fetch(@size) do |v| + raise ArgumentError, "#{self.class}: unknown size :#{v}. Valid: #{SIZES.keys.join(', ')}" + end + + # Strip intrinsic dimensions so CSS controls size; w-auto preserves aspect ratio. + normalized = raw_svg + .sub(/\swidth="[^"]*"/, "") + .sub(/\sheight="[^"]*"/, "") + + merged_class = classes("#{size_class} w-auto", consumer: @class) + + # Inject / merge our class attribute into the root . + if normalized =~ /]*)\sclass="([^"]*)"/ + normalized = normalized.sub(/]*)\sclass="([^"]*)"/) do + pre = Regexp.last_match(1) + existing = Regexp.last_match(2) + %(. + attr_str = @html_attrs.map { |k, v| %(#{k.to_s.tr('_', '-')}="#{ERB::Util.html_escape(v)}") }.join(" ") + if !attr_str.empty? + normalized = normalized.sub(" "amex-color", + "2:832" => "cash-dollar-color", + "2:836" => "paypal-color", + "2:845" => "applepay-color", + "2:849" => "visa-color", + "2:852" => "discover-color", + "2:864" => "cash-color", + "2:868" => "card-default-color", + "2:872" => "mastercard-color", + + # Payment / fill variants + "2:879" => "amex-fill", + "2:883" => "cash-dollar-fill", + "2:886" => "paypal-fill", + "2:889" => "cash-fill", + "2:892" => "applepay-fill", + "2:895" => "visa-fill", + "2:898" => "discover-fill", + "2:901" => "card-default-fill", + "2:905" => "mastercard-fill", + + # Payment / outline variants + "2:910" => "amex-outline", + "2:914" => "cash-dollar-outline", + "2:917" => "paypal-outline", + "2:920" => "applepay-outline", + "2:923" => "visa-outline", + "2:926" => "cash-outline", + "2:929" => "card-default-outline", + "2:933" => "discover-outline", + "2:936" => "mastercard-outline", + + # Social / main row + "2:944" => "facebook-color", + "2:947" => "youtube-color", + "2:950" => "instagram-color", + "2:955" => "google-color", + "2:960" => "linkedin-color", + "2:964" => "apple-color", + "2:966" => "snapchat-color", + "2:969" => "pinterest-color", + "2:972" => "medium-color", + "2:976" => "angelist-color", + "2:1019" => "slack-color", + + # Social / second row + "2:979" => "dribbble-color", + "2:984" => "figma-color", + "2:990" => "discord-color", + "2:994" => "clubhouse-color", + "2:997" => "tumblr-color", + "2:1000" => "telegram-color", + "2:1004" => "tiktok-color", + "2:1008" => "vk-color", + "2:1011" => "signal-color", + "2:1013" => "reddit-color", + "2:1016" => "github-color", + + # Social / tools row + "2:1025" => "fb-messenger-color", + "2:1028" => "skype-color", + "2:1034" => "spectrum-color", + "2:1037" => "zoom-color", + "2:1043" => "facetime-color", + "2:1046" => "google-meet-color", + "2:1057" => "behance-color", + "2:1060" => "invision-color", + "2:1063" => "apple-brand", + "2:1066" => "microsoft-color" +}.freeze + +def die(msg) + warn msg + exit 1 +end + +token = ENV["FIGMA_ACCESS_TOKEN"] || die("Set FIGMA_ACCESS_TOKEN (https://www.figma.com/settings)") + +FileUtils.mkdir_p(OUTPUT_DIR) + +# Figma's image endpoint supports up to ~1000 node IDs per call, but rate +# limits encourage batching. We'll batch in groups of 20. +def fetch_image_urls(token, file_key, ids) + uri = URI("https://api.figma.com/v1/images/#{file_key}?ids=#{ids.join(',')}&format=svg") + req = Net::HTTP::Get.new(uri) + req["X-Figma-Token"] = token + + res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } + raise "Figma API error: #{res.code} #{res.body}" unless res.is_a?(Net::HTTPSuccess) + + body = JSON.parse(res.body) + if body["err"] + raise "Figma returned err: #{body['err']}" + end + body["images"] +end + +def download(url) + uri = URI(url) + res = Net::HTTP.get_response(uri) + raise "Download failed: #{res.code} #{url}" unless res.is_a?(Net::HTTPSuccess) + + res.body +end + +ids = NODES.keys +puts "Fetching #{ids.size} brand icon export URLs from Figma..." + +urls = {} +ids.each_slice(20) do |batch| + urls.merge!(fetch_image_urls(token, FILE_KEY, batch)) + sleep 0.25 # polite throttle +end + +missing = ids.reject { |id| urls[id] } +unless missing.empty? + warn "Missing export URLs for: #{missing.map { |id| "#{id} (#{NODES[id]})" }.join(', ')}" +end + +downloaded = 0 +NODES.each do |node_id, filename| + url = urls[node_id] + next unless url + + svg = download(url) + path = File.join(OUTPUT_DIR, "#{filename}.svg") + File.write(path, svg) + downloaded += 1 + puts " wrote #{filename}.svg" +end + +puts "Done. Wrote #{downloaded}/#{NODES.size} SVGs to #{OUTPUT_DIR}" +puts "Review, then `git add app/assets/images/shipwright/brand && git commit`." diff --git a/test/components/previews/shipwright/brand_icon_component_preview.rb b/test/components/previews/shipwright/brand_icon_component_preview.rb new file mode 100644 index 0000000..4faf874 --- /dev/null +++ b/test/components/previews/shipwright/brand_icon_component_preview.rb @@ -0,0 +1,23 @@ +module Shipwright + class BrandIconComponentPreview < Lookbook::Preview + # @label Gallery (all installed brand icons) + def gallery + render_with_template + end + + # @label Sizes + def sizes + render_with_template + end + + # @label Rounded + def rounded + render_with_template + end + + # @label Empty state (no Figma export yet) + def empty_state + render_with_template + end + end +end diff --git a/test/components/previews/shipwright/brand_icon_component_preview/empty_state.html.erb b/test/components/previews/shipwright/brand_icon_component_preview/empty_state.html.erb new file mode 100644 index 0000000..e68e489 --- /dev/null +++ b/test/components/previews/shipwright/brand_icon_component_preview/empty_state.html.erb @@ -0,0 +1,13 @@ +
+

Brand icons are populated by a one-time export from Figma.

+

+ Run the exporter with a Figma personal access token + (get one here): +

+
FIGMA_ACCESS_TOKEN=xxx ruby script/export_brand_icons.rb
+

+ SVGs are written to app/assets/images/shipwright/brand/ and loaded automatically + on next app boot. Rendering without a matching SVG raises ArgumentError with the + full list of available icons. +

+
diff --git a/test/components/previews/shipwright/brand_icon_component_preview/gallery.html.erb b/test/components/previews/shipwright/brand_icon_component_preview/gallery.html.erb new file mode 100644 index 0000000..ab8d68f --- /dev/null +++ b/test/components/previews/shipwright/brand_icon_component_preview/gallery.html.erb @@ -0,0 +1,20 @@ +<% names = Shipwright::BrandIconComponent.icon_names %> +
+

+ <% if names.any? %> + <%= names.size %> brand icons installed. Payment + social icons exported from Shipwright Pro Figma. + <% else %> + No brand icons installed yet. Run + ruby script/export_brand_icons.rb + with a FIGMA_ACCESS_TOKEN to populate. + <% end %> +

+
+ <% names.each do |name| %> +
+ <%= render Shipwright::BrandIconComponent.new(name: name) %> + <%= name %> +
+ <% end %> +
+
diff --git a/test/components/previews/shipwright/brand_icon_component_preview/rounded.html.erb b/test/components/previews/shipwright/brand_icon_component_preview/rounded.html.erb new file mode 100644 index 0000000..b82c07b --- /dev/null +++ b/test/components/previews/shipwright/brand_icon_component_preview/rounded.html.erb @@ -0,0 +1,9 @@ +<% name = Shipwright::BrandIconComponent.icon_names.first %> +<% if name %> +
+ <%= render Shipwright::BrandIconComponent.new(name: name, size: :lg, class: "rounded-md overflow-hidden") %> + <%= render Shipwright::BrandIconComponent.new(name: name, size: :lg, class: "rounded-full overflow-hidden") %> +
+<% else %> +

No brand icons installed — run the exporter.

+<% end %> diff --git a/test/components/previews/shipwright/brand_icon_component_preview/sizes.html.erb b/test/components/previews/shipwright/brand_icon_component_preview/sizes.html.erb new file mode 100644 index 0000000..3026980 --- /dev/null +++ b/test/components/previews/shipwright/brand_icon_component_preview/sizes.html.erb @@ -0,0 +1,10 @@ +<% name = Shipwright::BrandIconComponent.icon_names.first %> +<% if name %> +
+ <%= render Shipwright::BrandIconComponent.new(name: name, size: :sm) %> + <%= render Shipwright::BrandIconComponent.new(name: name, size: :md) %> + <%= render Shipwright::BrandIconComponent.new(name: name, size: :lg) %> +
+<% else %> +

No brand icons installed — run the exporter.

+<% end %> diff --git a/test/components/shipwright/brand_icon_component_test.rb b/test/components/shipwright/brand_icon_component_test.rb new file mode 100644 index 0000000..03f965d --- /dev/null +++ b/test/components/shipwright/brand_icon_component_test.rb @@ -0,0 +1,108 @@ +require "test_helper" + +class Shipwright::BrandIconComponentTest < ViewComponent::TestCase + setup do + Shipwright::BrandIconComponent.asset_path = Pathname.new(__dir__).join("../../fixtures/brand_icons").expand_path + Shipwright::BrandIconComponent.reset_cache! + end + + teardown do + Shipwright::BrandIconComponent.asset_path = Shipwright::BrandIconComponent::DEFAULT_ASSET_PATH + Shipwright::BrandIconComponent.reset_cache! + end + + def test_renders_svg_from_fixture + render_inline(Shipwright::BrandIconComponent.new(name: :placeholder)) + + assert_selector "svg" + assert_selector "svg rect[data-testid='placeholder-rect']" + end + + def test_default_size_is_h_6 + render_inline(Shipwright::BrandIconComponent.new(name: :placeholder)) + + assert_selector "svg.h-6" + end + + def test_small_size_is_h_4 + render_inline(Shipwright::BrandIconComponent.new(name: :placeholder, size: :sm)) + + assert_selector "svg.h-4" + end + + def test_large_size_is_h_8 + render_inline(Shipwright::BrandIconComponent.new(name: :placeholder, size: :lg)) + + assert_selector "svg.h-8" + end + + def test_width_auto_preserves_aspect_ratio + render_inline(Shipwright::BrandIconComponent.new(name: :"wide-brand")) + + assert_selector "svg.w-auto" + end + + def test_intrinsic_width_and_height_are_stripped + render_inline(Shipwright::BrandIconComponent.new(name: :placeholder)) + + html = rendered_content + refute_match(/]+\swidth="38"/, html) + refute_match(/]+\sheight="38"/, html) + end + + def test_brand_colors_preserved + render_inline(Shipwright::BrandIconComponent.new(name: :placeholder)) + + assert_includes rendered_content, "#206ac7" + end + + def test_aria_hidden_by_default + render_inline(Shipwright::BrandIconComponent.new(name: :placeholder)) + + assert_selector "svg[aria-hidden='true']" + end + + def test_aria_label_removes_aria_hidden + render_inline(Shipwright::BrandIconComponent.new(name: :placeholder, "aria-label": "Brand logo")) + + assert_selector "svg[aria-label='Brand logo']" + assert_no_selector "svg[aria-hidden='true']" + end + + def test_consumer_class_merges + render_inline(Shipwright::BrandIconComponent.new(name: :placeholder, class: "rounded-full")) + + assert_selector "svg.rounded-full" + assert_selector "svg.h-6" + end + + def test_raises_on_unknown_icon + err = assert_raises(ArgumentError) do + render_inline(Shipwright::BrandIconComponent.new(name: :"does-not-exist")) + end + assert_includes err.message, "unknown brand icon" + end + + def test_raises_on_invalid_size + assert_raises(ArgumentError) do + render_inline(Shipwright::BrandIconComponent.new(name: :placeholder, size: :xl)) + end + end + + def test_icon_names_lists_fixtures + names = Shipwright::BrandIconComponent.icon_names + assert_includes names, :placeholder + assert_includes names, :"wide-brand" + end + + def test_empty_directory_raises_with_helpful_hint + Shipwright::BrandIconComponent.asset_path = Pathname.new(Dir.mktmpdir) + Shipwright::BrandIconComponent.reset_cache! + + err = assert_raises(ArgumentError) do + render_inline(Shipwright::BrandIconComponent.new(name: :anything)) + end + assert_includes err.message, "No brand icons loaded" + assert_includes err.message, "script/export_brand_icons.rb" + end +end diff --git a/test/dummy/app/assets/builds/tailwind.css b/test/dummy/app/assets/builds/tailwind.css index 51f3d63..28fe205 100644 --- a/test/dummy/app/assets/builds/tailwind.css +++ b/test/dummy/app/assets/builds/tailwind.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:"Manrope", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-normal:400;--radius-md:6px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-interactive-primary-default:#14161c;--color-interactive-primary-hover:#595d6a;--color-interactive-primary-pressed:#333747;--color-interactive-secondary-default:#fdfdfd;--color-interactive-disable:#eaeaea;--color-border-interactive:#14161c;--color-utility-negative-default:#b62e2e;--color-text-primary:#050507;--color-text-inverse:#fdfdfd;--color-text-disabled:#595d6a;--color-background-primary:#fdfdfd;--leading-2xs:1.25rem;--spacing-2xs:8px;--spacing-sm:16px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.grid{display:grid}.inline-flex{display:inline-flex}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-13{height:calc(var(--spacing) * 13)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-11{width:calc(var(--spacing) * 11)}.w-13{width:calc(var(--spacing) * 13)}.w-full{width:100%}.cursor-not-allowed{cursor:not-allowed}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2xs{gap:var(--spacing-2xs)}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-border-interactive{border-color:var(--color-border-interactive)}.bg-interactive-disable{background-color:var(--color-interactive-disable)}.bg-interactive-primary-default{background-color:var(--color-interactive-primary-default)}.bg-interactive-secondary-default{background-color:var(--color-interactive-secondary-default)}.bg-transparent{background-color:#0000}.bg-utility-negative-default{background-color:var(--color-utility-negative-default)}.p-0{padding:calc(var(--spacing) * 0)}.px-sm{padding-inline:var(--spacing-sm)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-2xs{--tw-leading:var(--leading-2xs);line-height:var(--leading-2xs)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-text-disabled{color:var(--color-text-disabled)}.text-text-inverse{color:var(--color-text-inverse)}.text-text-primary{color:var(--color-text-primary)}.italic{font-style:italic}.underline{text-decoration-line:underline}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:bg-interactive-disable:hover{background-color:var(--color-interactive-disable)}.hover\:bg-interactive-primary-hover:hover{background-color:var(--color-interactive-primary-hover)}.hover\:bg-utility-negative-default\/90:hover{background-color:#b62e2ee6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-utility-negative-default\/90:hover{background-color:color-mix(in oklab, var(--color-utility-negative-default) 90%, transparent)}}.hover\:text-text-disabled:hover{color:var(--color-text-disabled)}.hover\:text-text-inverse:hover{color:var(--color-text-inverse)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-interactive-primary-default:focus-visible{--tw-ring-color:var(--color-interactive-primary-default)}.focus-visible\:ring-utility-negative-default:focus-visible{--tw-ring-color:var(--color-utility-negative-default)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background-primary:focus-visible{--tw-ring-offset-color:var(--color-background-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:bg-interactive-disable:active{background-color:var(--color-interactive-disable)}.active\:bg-interactive-primary-pressed:active{background-color:var(--color-interactive-primary-pressed)}.active\:bg-utility-negative-default\/80:active{background-color:#b62e2ecc}@supports (color:color-mix(in lab, red, red)){.active\:bg-utility-negative-default\/80:active{background-color:color-mix(in oklab, var(--color-utility-negative-default) 80%, transparent)}}.active\:text-text-inverse:active{color:var(--color-text-inverse)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:"Manrope", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-normal:400;--radius-md:6px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-interactive-primary-default:#14161c;--color-interactive-primary-hover:#595d6a;--color-interactive-primary-pressed:#333747;--color-interactive-secondary-default:#fdfdfd;--color-interactive-disable:#eaeaea;--color-border-interactive:#14161c;--color-utility-negative-default:#b62e2e;--color-text-primary:#050507;--color-text-inverse:#fdfdfd;--color-text-disabled:#595d6a;--color-background-primary:#fdfdfd;--leading-2xs:1.25rem;--spacing-2xs:8px;--spacing-sm:16px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.grid{display:grid}.inline-flex{display:inline-flex}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-13{height:calc(var(--spacing) * 13)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-11{width:calc(var(--spacing) * 11)}.w-13{width:calc(var(--spacing) * 13)}.w-auto{width:auto}.w-full{width:100%}.cursor-not-allowed{cursor:not-allowed}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2xs{gap:var(--spacing-2xs)}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-border-interactive{border-color:var(--color-border-interactive)}.bg-interactive-disable{background-color:var(--color-interactive-disable)}.bg-interactive-primary-default{background-color:var(--color-interactive-primary-default)}.bg-interactive-secondary-default{background-color:var(--color-interactive-secondary-default)}.bg-transparent{background-color:#0000}.bg-utility-negative-default{background-color:var(--color-utility-negative-default)}.p-0{padding:calc(var(--spacing) * 0)}.px-sm{padding-inline:var(--spacing-sm)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-2xs{--tw-leading:var(--leading-2xs);line-height:var(--leading-2xs)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-text-disabled{color:var(--color-text-disabled)}.text-text-inverse{color:var(--color-text-inverse)}.text-text-primary{color:var(--color-text-primary)}.italic{font-style:italic}.underline{text-decoration-line:underline}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:bg-interactive-disable:hover{background-color:var(--color-interactive-disable)}.hover\:bg-interactive-primary-hover:hover{background-color:var(--color-interactive-primary-hover)}.hover\:bg-utility-negative-default\/90:hover{background-color:#b62e2ee6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-utility-negative-default\/90:hover{background-color:color-mix(in oklab, var(--color-utility-negative-default) 90%, transparent)}}.hover\:text-text-disabled:hover{color:var(--color-text-disabled)}.hover\:text-text-inverse:hover{color:var(--color-text-inverse)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-interactive-primary-default:focus-visible{--tw-ring-color:var(--color-interactive-primary-default)}.focus-visible\:ring-utility-negative-default:focus-visible{--tw-ring-color:var(--color-utility-negative-default)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background-primary:focus-visible{--tw-ring-offset-color:var(--color-background-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:bg-interactive-disable:active{background-color:var(--color-interactive-disable)}.active\:bg-interactive-primary-pressed:active{background-color:var(--color-interactive-primary-pressed)}.active\:bg-utility-negative-default\/80:active{background-color:#b62e2ecc}@supports (color:color-mix(in lab, red, red)){.active\:bg-utility-negative-default\/80:active{background-color:color-mix(in oklab, var(--color-utility-negative-default) 80%, transparent)}}.active\:text-text-inverse:active{color:var(--color-text-inverse)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} \ No newline at end of file diff --git a/test/fixtures/brand_icons/placeholder.svg b/test/fixtures/brand_icons/placeholder.svg new file mode 100644 index 0000000..734cc58 --- /dev/null +++ b/test/fixtures/brand_icons/placeholder.svg @@ -0,0 +1 @@ +TEST diff --git a/test/fixtures/brand_icons/wide-brand.svg b/test/fixtures/brand_icons/wide-brand.svg new file mode 100644 index 0000000..9c58641 --- /dev/null +++ b/test/fixtures/brand_icons/wide-brand.svg @@ -0,0 +1 @@ + From 2fe9721979c7c7e77d96a709435c772d9cc40a6e Mon Sep 17 00:00:00 2001 From: Jordan Burke Date: Sat, 18 Apr 2026 17:26:53 -0400 Subject: [PATCH 4/5] feat: add AccordionComponent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single collapsible section using native HTML
/ — no JavaScript required. Stack multiple AccordionComponents for an FAQ-style group; native semantics handle toggle, keyboard navigation, and accessibility out of the box. API: <%= render(Shipwright::AccordionComponent.new(title: "FAQ question")) do %> Answer content here. <% end %> <%# Start expanded %> <%= render(Shipwright::AccordionComponent.new(title: "T", open: true)) { "..." } %> Props: - title: required string rendered in the summary - open: bool (default false) — sets the
attribute - class: consumer override on the
element - **html_attrs: pass-through (id, data-*, etc.) Content block → body text rendered below the summary. Styling matches Shipwright Pro Figma (node 2013:5693): - Default: text-interactive-primary-default, border-border-primary - Hover: bg-background-secondary, text-interactive-primary-hover - Focus-visible: 2px ring on interactive-primary-default - Chevron icon (IconComponent name: :chevron-down) rotates 180deg when open via Tailwind's group-open: modifier — works natively with
, no JS state management - Default summary marker hidden cross-browser via list-none + ::-webkit-details-marker New tokens: - --color-border-primary: #eaeaea (subtle gray borders, matches Shipwright Pro Border/Primary) - --color-background-secondary: #f3f3f3 (hover surface, matches Shipwright Pro Interactive/Secondary-Hover) BaseComponent MERGER config updated with both new color tokens. Uses IconComponent for the chevron (branch includes the icon-component merge for that dependency). 12 new tests pass. Total suite: 64 tests, 141 assertions. Lookbook previews: default, initially_open, stacked_group, long_body. --- app/assets/tailwind/shipwright/engine.css | 2 + .../shipwright/accordion_component.html.erb | 9 ++ .../shipwright/accordion_component.rb | 57 ++++++++++++ app/components/shipwright/base_component.rb | 2 + .../shipwright/accordion_component_preview.rb | 28 ++++++ .../long_body.html.erb | 18 ++++ .../stacked_group.html.erb | 11 +++ .../shipwright/accordion_component_test.rb | 91 +++++++++++++++++++ test/dummy/app/assets/builds/tailwind.css | 2 +- .../dummy/app/views/pages/components.html.erb | 13 +++ 10 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 app/components/shipwright/accordion_component.html.erb create mode 100644 app/components/shipwright/accordion_component.rb create mode 100644 test/components/previews/shipwright/accordion_component_preview.rb create mode 100644 test/components/previews/shipwright/accordion_component_preview/long_body.html.erb create mode 100644 test/components/previews/shipwright/accordion_component_preview/stacked_group.html.erb create mode 100644 test/components/shipwright/accordion_component_test.rb diff --git a/app/assets/tailwind/shipwright/engine.css b/app/assets/tailwind/shipwright/engine.css index db2420a..9648386 100644 --- a/app/assets/tailwind/shipwright/engine.css +++ b/app/assets/tailwind/shipwright/engine.css @@ -25,6 +25,7 @@ /* Border colors */ --color-border-interactive: #14161c; + --color-border-primary: #eaeaea; /* Utility colors (destructive, success, warning, info, etc.) */ --color-utility-negative-default: #b62e2e; @@ -36,6 +37,7 @@ /* Background colors */ --color-background-primary: #fdfdfd; + --color-background-secondary: #f3f3f3; /* Typography — font loading is the consuming app's responsibility */ --font-sans: "Manrope", ui-sans-serif, system-ui, sans-serif; diff --git a/app/components/shipwright/accordion_component.html.erb b/app/components/shipwright/accordion_component.html.erb new file mode 100644 index 0000000..c673980 --- /dev/null +++ b/app/components/shipwright/accordion_component.html.erb @@ -0,0 +1,9 @@ +<%= content_tag :details, class: container_classes, open: open_attr, **html_attrs do %> + + <%= render Shipwright::IconComponent.new(name: :"chevron-down", size: :sm, class: chevron_classes) %> + <%= title %> + +
+ <%= content %> +
+<% end %> diff --git a/app/components/shipwright/accordion_component.rb b/app/components/shipwright/accordion_component.rb new file mode 100644 index 0000000..558339f --- /dev/null +++ b/app/components/shipwright/accordion_component.rb @@ -0,0 +1,57 @@ +module Shipwright + # A single collapsible section. Uses native
/ for + # toggle behavior, keyboard support, and accessibility — no JavaScript + # required. Stack multiple AccordionComponents to build a group. + # + # Usage: + # <%= render(Shipwright::AccordionComponent.new(title: "FAQ question")) do %> + # Answer content here. + # <% end %> + # + # <%# Initially open %> + # <%= render(Shipwright::AccordionComponent.new(title: "T", open: true)) { "..." } %> + class AccordionComponent < BaseComponent + # `class:` is a Ruby reserved word used as a keyword arg — requires + # binding.local_variable_get. Shipwright convention. + def initialize(title:, open: false, class: nil, **html_attrs) + @title = title + @open = open + @class = binding.local_variable_get(:class) + @html_attrs = html_attrs + end + + attr_reader :title, :html_attrs + + def open_attr + @open + end + + def container_classes + classes( + "group block border border-border-primary rounded-md overflow-hidden font-sans", + consumer: @class + ) + end + + # Classes applied to . Hides the default marker cross-browser, + # adds padding, typography, and interactive state visuals. + def summary_classes + [ + "flex items-center gap-3 px-4 py-3 cursor-pointer select-none", + "text-base leading-5 text-interactive-primary-default", + "transition-colors", + "hover:bg-background-secondary hover:text-interactive-primary-hover", + "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-interactive-primary-default focus-visible:ring-inset", + "list-none [&::-webkit-details-marker]:hidden" + ].join(" ") + end + + def body_classes + "px-4 pb-3 pt-0 text-sm leading-5 text-text-primary" + end + + def chevron_classes + "transition-transform duration-200 group-open:rotate-180" + end + end +end diff --git a/app/components/shipwright/base_component.rb b/app/components/shipwright/base_component.rb index f75b669..5fd9263 100644 --- a/app/components/shipwright/base_component.rb +++ b/app/components/shipwright/base_component.rb @@ -13,11 +13,13 @@ class BaseComponent < ViewComponent::Base interactive-secondary-default interactive-disable border-interactive + border-primary utility-negative-default text-primary text-inverse text-disabled background-primary + background-secondary ], "radius" => %w[md] } diff --git a/test/components/previews/shipwright/accordion_component_preview.rb b/test/components/previews/shipwright/accordion_component_preview.rb new file mode 100644 index 0000000..8ff9ac7 --- /dev/null +++ b/test/components/previews/shipwright/accordion_component_preview.rb @@ -0,0 +1,28 @@ +module Shipwright + class AccordionComponentPreview < Lookbook::Preview + # @label Default (closed) + def default + render(Shipwright::AccordionComponent.new(title: "Accordion title")) do + "Lorem ipsum dolor sit amet consectetur. Eget aenean eu in mattis ultrices tellus arcu. " \ + "Sed et turpis volutpat tristique risus. Elementum id interdum tortor faucibus ut." + end + end + + # @label Initially open + def initially_open + render(Shipwright::AccordionComponent.new(title: "Accordion title", open: true)) do + "Starts expanded. Great for the first item in an FAQ." + end + end + + # @label Stacked group + def stacked_group + render_with_template + end + + # @label Long body + def long_body + render_with_template + end + end +end diff --git a/test/components/previews/shipwright/accordion_component_preview/long_body.html.erb b/test/components/previews/shipwright/accordion_component_preview/long_body.html.erb new file mode 100644 index 0000000..eac456f --- /dev/null +++ b/test/components/previews/shipwright/accordion_component_preview/long_body.html.erb @@ -0,0 +1,18 @@ +
+ <%= render(Shipwright::AccordionComponent.new(title: "Terms and conditions", open: true)) do %> +

+ Lorem ipsum dolor sit amet consectetur. Eget aenean eu in mattis ultrices tellus arcu. + Sed et turpis volutpat tristique risus. Elementum id interdum tortor faucibus ut. + Maecenas eu pellentesque etiam vel. +

+

+ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip + ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit + esse cillum dolore eu fugiat nulla pariatur. +

+

+ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt + mollit anim id est laborum. +

+ <% end %> +
diff --git a/test/components/previews/shipwright/accordion_component_preview/stacked_group.html.erb b/test/components/previews/shipwright/accordion_component_preview/stacked_group.html.erb new file mode 100644 index 0000000..4acf638 --- /dev/null +++ b/test/components/previews/shipwright/accordion_component_preview/stacked_group.html.erb @@ -0,0 +1,11 @@ +
+ <%= render(Shipwright::AccordionComponent.new(title: "What is Shipwright?")) do %> + A Rails ViewComponent library implementing the Shipwright Pro design system. + <% end %> + <%= render(Shipwright::AccordionComponent.new(title: "How do I install it?")) do %> + Add it to your Gemfile from the private repo and follow the setup instructions in the README. + <% end %> + <%= render(Shipwright::AccordionComponent.new(title: "Does it work with React Native?")) do %> + No — this library is Rails-only. See the sister project shipwright-ui for React + React Native. + <% end %> +
diff --git a/test/components/shipwright/accordion_component_test.rb b/test/components/shipwright/accordion_component_test.rb new file mode 100644 index 0000000..d58c6f3 --- /dev/null +++ b/test/components/shipwright/accordion_component_test.rb @@ -0,0 +1,91 @@ +require "test_helper" + +class Shipwright::AccordionComponentTest < ViewComponent::TestCase + def test_renders_title_and_body + render_inline(Shipwright::AccordionComponent.new(title: "What is it?")) { "A body." } + + assert_selector "details" + assert_selector "summary", text: "What is it?" + assert_text "A body." + end + + def test_closed_by_default + render_inline(Shipwright::AccordionComponent.new(title: "T")) { "B" } + + assert_no_selector "details[open]" + end + + def test_open_param_renders_as_open + render_inline(Shipwright::AccordionComponent.new(title: "T", open: true)) { "B" } + + assert_selector "details[open]" + end + + def test_chevron_icon_rendered + render_inline(Shipwright::AccordionComponent.new(title: "T")) { "B" } + + # Uses IconComponent, so an will appear inside + assert_selector "summary svg" + end + + def test_chevron_rotates_when_open + render_inline(Shipwright::AccordionComponent.new(title: "T", open: true)) { "B" } + + html = rendered_content + assert_includes html, "group-open:rotate-180" + end + + def test_summary_has_border_and_padding_classes + render_inline(Shipwright::AccordionComponent.new(title: "T")) { "B" } + + html = rendered_content + assert_includes html, "border-border-primary" + assert_includes html, "px-4" + assert_includes html, "py-3" + end + + def test_hover_and_focus_classes_present + render_inline(Shipwright::AccordionComponent.new(title: "T")) { "B" } + + html = rendered_content + assert_includes html, "hover:bg-background-secondary" + assert_includes html, "focus-visible:" + end + + def test_summary_hides_default_marker + render_inline(Shipwright::AccordionComponent.new(title: "T")) { "B" } + + html = rendered_content + assert_includes html, "list-none" + assert_includes html, "::-webkit-details-marker" + end + + def test_consumer_class_merges + render_inline(Shipwright::AccordionComponent.new(title: "T", class: "shadow-lg")) { "B" } + + assert_selector "details.shadow-lg" + end + + def test_passes_html_attributes_to_details + render_inline(Shipwright::AccordionComponent.new(title: "T", id: "faq-1", data: { section: "faq" })) { "B" } + + assert_selector "details#faq-1[data-section='faq']" + end + + def test_typography_applied_to_title + render_inline(Shipwright::AccordionComponent.new(title: "Title")) { "B" } + + html = rendered_content + # Title should have Manrope + base font size + assert_includes html, "text-base" + assert_includes html, "font-sans" + end + + def test_body_rendered_below_summary + render_inline(Shipwright::AccordionComponent.new(title: "T", open: true)) { "Body contents here." } + + # Body should be a sibling of summary inside details + assert_selector "details > summary" + assert_selector "details > div", text: "Body contents here." + end +end diff --git a/test/dummy/app/assets/builds/tailwind.css b/test/dummy/app/assets/builds/tailwind.css index 28fe205..673f1eb 100644 --- a/test/dummy/app/assets/builds/tailwind.css +++ b/test/dummy/app/assets/builds/tailwind.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:"Manrope", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-normal:400;--radius-md:6px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-interactive-primary-default:#14161c;--color-interactive-primary-hover:#595d6a;--color-interactive-primary-pressed:#333747;--color-interactive-secondary-default:#fdfdfd;--color-interactive-disable:#eaeaea;--color-border-interactive:#14161c;--color-utility-negative-default:#b62e2e;--color-text-primary:#050507;--color-text-inverse:#fdfdfd;--color-text-disabled:#595d6a;--color-background-primary:#fdfdfd;--leading-2xs:1.25rem;--spacing-2xs:8px;--spacing-sm:16px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.grid{display:grid}.inline-flex{display:inline-flex}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-13{height:calc(var(--spacing) * 13)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-11{width:calc(var(--spacing) * 11)}.w-13{width:calc(var(--spacing) * 13)}.w-auto{width:auto}.w-full{width:100%}.cursor-not-allowed{cursor:not-allowed}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2xs{gap:var(--spacing-2xs)}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-border-interactive{border-color:var(--color-border-interactive)}.bg-interactive-disable{background-color:var(--color-interactive-disable)}.bg-interactive-primary-default{background-color:var(--color-interactive-primary-default)}.bg-interactive-secondary-default{background-color:var(--color-interactive-secondary-default)}.bg-transparent{background-color:#0000}.bg-utility-negative-default{background-color:var(--color-utility-negative-default)}.p-0{padding:calc(var(--spacing) * 0)}.px-sm{padding-inline:var(--spacing-sm)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-2xs{--tw-leading:var(--leading-2xs);line-height:var(--leading-2xs)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-text-disabled{color:var(--color-text-disabled)}.text-text-inverse{color:var(--color-text-inverse)}.text-text-primary{color:var(--color-text-primary)}.italic{font-style:italic}.underline{text-decoration-line:underline}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:bg-interactive-disable:hover{background-color:var(--color-interactive-disable)}.hover\:bg-interactive-primary-hover:hover{background-color:var(--color-interactive-primary-hover)}.hover\:bg-utility-negative-default\/90:hover{background-color:#b62e2ee6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-utility-negative-default\/90:hover{background-color:color-mix(in oklab, var(--color-utility-negative-default) 90%, transparent)}}.hover\:text-text-disabled:hover{color:var(--color-text-disabled)}.hover\:text-text-inverse:hover{color:var(--color-text-inverse)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-interactive-primary-default:focus-visible{--tw-ring-color:var(--color-interactive-primary-default)}.focus-visible\:ring-utility-negative-default:focus-visible{--tw-ring-color:var(--color-utility-negative-default)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background-primary:focus-visible{--tw-ring-offset-color:var(--color-background-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:bg-interactive-disable:active{background-color:var(--color-interactive-disable)}.active\:bg-interactive-primary-pressed:active{background-color:var(--color-interactive-primary-pressed)}.active\:bg-utility-negative-default\/80:active{background-color:#b62e2ecc}@supports (color:color-mix(in lab, red, red)){.active\:bg-utility-negative-default\/80:active{background-color:color-mix(in oklab, var(--color-utility-negative-default) 80%, transparent)}}.active\:text-text-inverse:active{color:var(--color-text-inverse)}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:"Manrope", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-normal:400;--radius-md:6px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-interactive-primary-default:#14161c;--color-interactive-primary-hover:#595d6a;--color-interactive-primary-pressed:#333747;--color-interactive-secondary-default:#fdfdfd;--color-interactive-disable:#eaeaea;--color-border-interactive:#14161c;--color-border-primary:#eaeaea;--color-utility-negative-default:#b62e2e;--color-text-primary:#050507;--color-text-inverse:#fdfdfd;--color-text-disabled:#595d6a;--color-background-primary:#fdfdfd;--color-background-secondary:#f3f3f3;--leading-2xs:1.25rem;--spacing-2xs:8px;--spacing-sm:16px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline-flex{display:inline-flex}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-13{height:calc(var(--spacing) * 13)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-11{width:calc(var(--spacing) * 11)}.w-13{width:calc(var(--spacing) * 13)}.w-auto{width:auto}.w-full{width:100%}.flex-1{flex:1}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2xs{gap:var(--spacing-2xs)}.gap-3{gap:calc(var(--spacing) * 3)}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-border-interactive{border-color:var(--color-border-interactive)}.border-border-primary{border-color:var(--color-border-primary)}.bg-interactive-disable{background-color:var(--color-interactive-disable)}.bg-interactive-primary-default{background-color:var(--color-interactive-primary-default)}.bg-interactive-secondary-default{background-color:var(--color-interactive-secondary-default)}.bg-transparent{background-color:#0000}.bg-utility-negative-default{background-color:var(--color-utility-negative-default)}.p-0{padding:calc(var(--spacing) * 0)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-sm{padding-inline:var(--spacing-sm)}.py-3{padding-block:calc(var(--spacing) * 3)}.pt-0{padding-top:calc(var(--spacing) * 0)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-2xs{--tw-leading:var(--leading-2xs);line-height:var(--leading-2xs)}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-interactive-primary-default{color:var(--color-interactive-primary-default)}.text-text-disabled{color:var(--color-text-disabled)}.text-text-inverse{color:var(--color-text-inverse)}.text-text-primary{color:var(--color-text-primary)}.italic{font-style:italic}.underline{text-decoration-line:underline}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;user-select:none}.group-open\:rotate-180:is(:where(.group):is([open],:popover-open,:open) *){rotate:180deg}@media (hover:hover){.hover\:bg-background-secondary:hover{background-color:var(--color-background-secondary)}.hover\:bg-interactive-disable:hover{background-color:var(--color-interactive-disable)}.hover\:bg-interactive-primary-hover:hover{background-color:var(--color-interactive-primary-hover)}.hover\:bg-utility-negative-default\/90:hover{background-color:#b62e2ee6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-utility-negative-default\/90:hover{background-color:color-mix(in oklab, var(--color-utility-negative-default) 90%, transparent)}}.hover\:text-interactive-primary-hover:hover{color:var(--color-interactive-primary-hover)}.hover\:text-text-disabled:hover{color:var(--color-text-disabled)}.hover\:text-text-inverse:hover{color:var(--color-text-inverse)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-interactive-primary-default:focus-visible{--tw-ring-color:var(--color-interactive-primary-default)}.focus-visible\:ring-utility-negative-default:focus-visible{--tw-ring-color:var(--color-utility-negative-default)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background-primary:focus-visible{--tw-ring-offset-color:var(--color-background-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.focus-visible\:ring-inset:focus-visible{--tw-ring-inset:inset}.active\:bg-interactive-disable:active{background-color:var(--color-interactive-disable)}.active\:bg-interactive-primary-pressed:active{background-color:var(--color-interactive-primary-pressed)}.active\:bg-utility-negative-default\/80:active{background-color:#b62e2ecc}@supports (color:color-mix(in lab, red, red)){.active\:bg-utility-negative-default\/80:active{background-color:color-mix(in oklab, var(--color-utility-negative-default) 80%, transparent)}}.active\:text-text-inverse:active{color:var(--color-text-inverse)}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false} \ No newline at end of file diff --git a/test/dummy/app/views/pages/components.html.erb b/test/dummy/app/views/pages/components.html.erb index 1a6be39..56f11d0 100644 --- a/test/dummy/app/views/pages/components.html.erb +++ b/test/dummy/app/views/pages/components.html.erb @@ -69,4 +69,17 @@ <%= render Shipwright::IconComponent.new(name: :info, size: :md) %> <%= render Shipwright::IconComponent.new(name: :info, size: :lg) %>
+ +

Accordion

+
+ <%= render(Shipwright::AccordionComponent.new(title: "What is Shipwright?")) do %> + A Rails ViewComponent library implementing the Shipwright Pro design system. + <% end %> + <%= render(Shipwright::AccordionComponent.new(title: "How do I install it?", open: true)) do %> + Add it to your Gemfile and follow the setup instructions in the README. + <% end %> + <%= render(Shipwright::AccordionComponent.new(title: "Does it work with React Native?")) do %> + No — this is a Rails-only library. See shipwright-ui for React + React Native. + <% end %> +
From 98b3ec5ef771e0e5040437a27a54ffac436f9e1f Mon Sep 17 00:00:00 2001 From: Jordan Burke Date: Sat, 18 Apr 2026 17:46:42 -0400 Subject: [PATCH 5/5] feat(accordion): add CSS transition + body top padding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Animates open/close via the native
element's ::details-content pseudo-element, combined with interpolate-size: allow-keywords to enable transitioning to/from height: auto. No JavaScript required. - [&::details-content]:h-0 when collapsed, h-auto when [open] - 200ms transition on height + content-visibility (the latter needs transition-behavior allow-discrete which Tailwind 4 handles via the content-visibility transition entry) - Chevron rotation timing already matches at 200ms Body gets pt-2 (8px) for breathing room below the summary — matches Shipwright Pro's spacing scale. Browser support: Chromium 129+, Safari 18.2+ (late 2024). Firefox gracefully falls back to instant toggle until interpolate-size ships. No a11y impact —
still works natively on all browsers. --- app/components/shipwright/accordion_component.rb | 12 +++++++++++- test/dummy/app/assets/builds/tailwind.css | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/components/shipwright/accordion_component.rb b/app/components/shipwright/accordion_component.rb index 558339f..2048980 100644 --- a/app/components/shipwright/accordion_component.rb +++ b/app/components/shipwright/accordion_component.rb @@ -29,6 +29,14 @@ def open_attr def container_classes classes( "group block border border-border-primary rounded-md overflow-hidden font-sans", + # Height transition for open/close. + # Relies on interpolate-size + the ::details-content pseudo-element + # to animate from height 0 ↔ auto without JS measurement. + # Browsers without support (older Firefox) fall back to instant toggle. + "[interpolate-size:allow-keywords]", + "[&::details-content]:h-0 [&::details-content]:overflow-hidden", + "[&::details-content]:transition-[height,content-visibility] [&::details-content]:duration-200 [&::details-content]:ease-out", + "[&[open]::details-content]:h-auto", consumer: @class ) end @@ -47,7 +55,9 @@ def summary_classes end def body_classes - "px-4 pb-3 pt-0 text-sm leading-5 text-text-primary" + # pt-2 (8px / spacing-2xs) gives the body a small breathing gap below + # the summary's bottom padding. Matches Shipwright Pro's spacing scale. + "px-4 pb-3 pt-2 text-sm leading-5 text-text-primary" end def chevron_classes diff --git a/test/dummy/app/assets/builds/tailwind.css b/test/dummy/app/assets/builds/tailwind.css index 673f1eb..a96fad7 100644 --- a/test/dummy/app/assets/builds/tailwind.css +++ b/test/dummy/app/assets/builds/tailwind.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:"Manrope", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-normal:400;--radius-md:6px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-interactive-primary-default:#14161c;--color-interactive-primary-hover:#595d6a;--color-interactive-primary-pressed:#333747;--color-interactive-secondary-default:#fdfdfd;--color-interactive-disable:#eaeaea;--color-border-interactive:#14161c;--color-border-primary:#eaeaea;--color-utility-negative-default:#b62e2e;--color-text-primary:#050507;--color-text-inverse:#fdfdfd;--color-text-disabled:#595d6a;--color-background-primary:#fdfdfd;--color-background-secondary:#f3f3f3;--leading-2xs:1.25rem;--spacing-2xs:8px;--spacing-sm:16px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline-flex{display:inline-flex}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-13{height:calc(var(--spacing) * 13)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-11{width:calc(var(--spacing) * 11)}.w-13{width:calc(var(--spacing) * 13)}.w-auto{width:auto}.w-full{width:100%}.flex-1{flex:1}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2xs{gap:var(--spacing-2xs)}.gap-3{gap:calc(var(--spacing) * 3)}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-border-interactive{border-color:var(--color-border-interactive)}.border-border-primary{border-color:var(--color-border-primary)}.bg-interactive-disable{background-color:var(--color-interactive-disable)}.bg-interactive-primary-default{background-color:var(--color-interactive-primary-default)}.bg-interactive-secondary-default{background-color:var(--color-interactive-secondary-default)}.bg-transparent{background-color:#0000}.bg-utility-negative-default{background-color:var(--color-utility-negative-default)}.p-0{padding:calc(var(--spacing) * 0)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-sm{padding-inline:var(--spacing-sm)}.py-3{padding-block:calc(var(--spacing) * 3)}.pt-0{padding-top:calc(var(--spacing) * 0)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-2xs{--tw-leading:var(--leading-2xs);line-height:var(--leading-2xs)}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-interactive-primary-default{color:var(--color-interactive-primary-default)}.text-text-disabled{color:var(--color-text-disabled)}.text-text-inverse{color:var(--color-text-inverse)}.text-text-primary{color:var(--color-text-primary)}.italic{font-style:italic}.underline{text-decoration-line:underline}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;user-select:none}.group-open\:rotate-180:is(:where(.group):is([open],:popover-open,:open) *){rotate:180deg}@media (hover:hover){.hover\:bg-background-secondary:hover{background-color:var(--color-background-secondary)}.hover\:bg-interactive-disable:hover{background-color:var(--color-interactive-disable)}.hover\:bg-interactive-primary-hover:hover{background-color:var(--color-interactive-primary-hover)}.hover\:bg-utility-negative-default\/90:hover{background-color:#b62e2ee6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-utility-negative-default\/90:hover{background-color:color-mix(in oklab, var(--color-utility-negative-default) 90%, transparent)}}.hover\:text-interactive-primary-hover:hover{color:var(--color-interactive-primary-hover)}.hover\:text-text-disabled:hover{color:var(--color-text-disabled)}.hover\:text-text-inverse:hover{color:var(--color-text-inverse)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-interactive-primary-default:focus-visible{--tw-ring-color:var(--color-interactive-primary-default)}.focus-visible\:ring-utility-negative-default:focus-visible{--tw-ring-color:var(--color-utility-negative-default)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background-primary:focus-visible{--tw-ring-offset-color:var(--color-background-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.focus-visible\:ring-inset:focus-visible{--tw-ring-inset:inset}.active\:bg-interactive-disable:active{background-color:var(--color-interactive-disable)}.active\:bg-interactive-primary-pressed:active{background-color:var(--color-interactive-primary-pressed)}.active\:bg-utility-negative-default\/80:active{background-color:#b62e2ecc}@supports (color:color-mix(in lab, red, red)){.active\:bg-utility-negative-default\/80:active{background-color:color-mix(in oklab, var(--color-utility-negative-default) 80%, transparent)}}.active\:text-text-inverse:active{color:var(--color-text-inverse)}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:"Manrope", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-normal:400;--radius-md:6px;--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-interactive-primary-default:#14161c;--color-interactive-primary-hover:#595d6a;--color-interactive-primary-pressed:#333747;--color-interactive-secondary-default:#fdfdfd;--color-interactive-disable:#eaeaea;--color-border-interactive:#14161c;--color-border-primary:#eaeaea;--color-utility-negative-default:#b62e2e;--color-text-primary:#050507;--color-text-inverse:#fdfdfd;--color-text-disabled:#595d6a;--color-background-primary:#fdfdfd;--color-background-secondary:#f3f3f3;--leading-2xs:1.25rem;--spacing-2xs:8px;--spacing-sm:16px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.block{display:block}.flex{display:flex}.grid{display:grid}.inline-flex{display:inline-flex}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-13{height:calc(var(--spacing) * 13)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-11{width:calc(var(--spacing) * 11)}.w-13{width:calc(var(--spacing) * 13)}.w-auto{width:auto}.w-full{width:100%}.flex-1{flex:1}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2xs{gap:var(--spacing-2xs)}.gap-3{gap:calc(var(--spacing) * 3)}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-border-interactive{border-color:var(--color-border-interactive)}.border-border-primary{border-color:var(--color-border-primary)}.bg-interactive-disable{background-color:var(--color-interactive-disable)}.bg-interactive-primary-default{background-color:var(--color-interactive-primary-default)}.bg-interactive-secondary-default{background-color:var(--color-interactive-secondary-default)}.bg-transparent{background-color:#0000}.bg-utility-negative-default{background-color:var(--color-utility-negative-default)}.p-0{padding:calc(var(--spacing) * 0)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-sm{padding-inline:var(--spacing-sm)}.py-3{padding-block:calc(var(--spacing) * 3)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-2xs{--tw-leading:var(--leading-2xs);line-height:var(--leading-2xs)}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-interactive-primary-default{color:var(--color-interactive-primary-default)}.text-text-disabled{color:var(--color-text-disabled)}.text-text-inverse{color:var(--color-text-inverse)}.text-text-primary{color:var(--color-text-primary)}.italic{font-style:italic}.underline{text-decoration-line:underline}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;user-select:none}.\[interpolate-size\:allow-keywords\]{interpolate-size:allow-keywords}.group-open\:rotate-180:is(:where(.group):is([open],:popover-open,:open) *){rotate:180deg}@media (hover:hover){.hover\:bg-background-secondary:hover{background-color:var(--color-background-secondary)}.hover\:bg-interactive-disable:hover{background-color:var(--color-interactive-disable)}.hover\:bg-interactive-primary-hover:hover{background-color:var(--color-interactive-primary-hover)}.hover\:bg-utility-negative-default\/90:hover{background-color:#b62e2ee6}@supports (color:color-mix(in lab, red, red)){.hover\:bg-utility-negative-default\/90:hover{background-color:color-mix(in oklab, var(--color-utility-negative-default) 90%, transparent)}}.hover\:text-interactive-primary-hover:hover{color:var(--color-interactive-primary-hover)}.hover\:text-text-disabled:hover{color:var(--color-text-disabled)}.hover\:text-text-inverse:hover{color:var(--color-text-inverse)}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-interactive-primary-default:focus-visible{--tw-ring-color:var(--color-interactive-primary-default)}.focus-visible\:ring-utility-negative-default:focus-visible{--tw-ring-color:var(--color-utility-negative-default)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background-primary:focus-visible{--tw-ring-offset-color:var(--color-background-primary)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.focus-visible\:ring-inset:focus-visible{--tw-ring-inset:inset}.active\:bg-interactive-disable:active{background-color:var(--color-interactive-disable)}.active\:bg-interactive-primary-pressed:active{background-color:var(--color-interactive-primary-pressed)}.active\:bg-utility-negative-default\/80:active{background-color:#b62e2ecc}@supports (color:color-mix(in lab, red, red)){.active\:bg-utility-negative-default\/80:active{background-color:color-mix(in oklab, var(--color-utility-negative-default) 80%, transparent)}}.active\:text-text-inverse:active{color:var(--color-text-inverse)}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}.\[\&\:\:details-content\]\:h-0::details-content{height:calc(var(--spacing) * 0)}.\[\&\:\:details-content\]\:overflow-hidden::details-content{overflow:hidden}.\[\&\:\:details-content\]\:transition-\[height\,content-visibility\]::details-content{transition-property:height,content-visibility;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.\[\&\:\:details-content\]\:duration-200::details-content{--tw-duration:.2s;transition-duration:.2s}.\[\&\:\:details-content\]\:ease-out::details-content{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.\[\&\[open\]\:\:details-content\]\:h-auto[open]::details-content{height:auto}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false} \ No newline at end of file