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..2048980 --- /dev/null +++ b/app/components/shipwright/accordion_component.rb @@ -0,0 +1,67 @@ +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", + # 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 + + # 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 + # 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 + "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/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("
+ <%= 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/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/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..daefa2b --- /dev/null +++ b/test/components/previews/shipwright/icon_component_preview/gallery.html.erb @@ -0,0 +1,14 @@ +
+

+ <%= 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/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/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/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/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..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}}}@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;--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 diff --git a/test/dummy/app/views/pages/components.html.erb b/test/dummy/app/views/pages/components.html.erb index de3e4fc..56f11d0 100644 --- a/test/dummy/app/views/pages/components.html.erb +++ b/test/dummy/app/views/pages/components.html.erb @@ -49,4 +49,37 @@
<%= render Shipwright::ButtonComponent.new(class: "w-full") do %>Full Width Override<% end %>
+ +

Icons — sample (full gallery in Lookbook)

+

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

+
+ <% [: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 %> +
+ <% 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) %> +
+ +

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 %> +
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 @@ +