Skip to content

Commit 6c8c75a

Browse files
feat(ui): add semantic design token system for theming
Implements a comprehensive theming system using semantic design tokens that makes Plutonium components easy to customize while maintaining design consistency. Key changes: - Replace numeric spacing (p-1, p-2, p-4) with semantic tokens (p-xs, p-sm, p-md, p-lg) across all 50+ UI components - Add semantic color tokens (surface, page, elevated, interactive) for consistent light/dark mode theming - Update all CSS integrations (SlimSelect, EasyMDE, Flatpickr, IntlTelInput) to use semantic tokens - Reduce border radius values for more minimal, modern aesthetic - Add comprehensive theming documentation with setup guide and customization examples - Standardize gray palette for dark mode (avoiding brown/warm tones) Benefits: - Cross-property consistency: same size name (md) works across all utilities (p-md = gap-md = my-md) - Automatic dark mode: semantic colors adapt without manual dark: variants - Easy customization: override tokens in Tailwind config to theme entire application - Backward compatible: Plutonium remains self-contained by default, asset integration optional
1 parent 5ab40be commit 6c8c75a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+753
-216
lines changed

docs/guide/theming.md

Lines changed: 431 additions & 0 deletions
Large diffs are not rendered by default.

lib/plutonium/ui/action_button.rb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,29 @@ def button_classes
7070
base_classes,
7171
color_classes,
7272
size_classes,
73-
-> { @action.icon && @variant != :table } => "space-x-1"
73+
-> { @action.icon && @variant != :table } => "space-x-xs"
7474
)
7575
end
7676

7777
def base_classes
7878
if @variant == :table
79-
"inline-flex items-center justify-center py-1 px-2 rounded-lg focus:outline-none focus:ring-2"
79+
tokens(
80+
theme_class(:button, variant: :table),
81+
"inline-flex items-center justify-center py-xs px-sm rounded-sm focus:outline-none focus:ring-2"
82+
)
8083
else
81-
"flex items-center justify-center px-4 py-2 text-sm font-medium rounded-lg focus:outline-none focus:ring-4"
84+
tokens(
85+
theme_class(:button),
86+
"flex items-center justify-center px-md py-sm text-sm font-medium rounded-sm focus:outline-none focus:ring-4"
87+
)
8288
end
8389
end
8490

8591
def icon_classes
8692
if @variant == :table
87-
"h-4 w-4 mr-1"
93+
"h-4 w-4 mr-xs"
8894
else
89-
"h-3.5 w-3.5 -ml-1"
95+
"h-3.5 w-3.5 -ml-xs"
9096
end
9197
end
9298

lib/plutonium/ui/block.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ class Block < Plutonium::UI::Component::Base
44
def view_template(&)
55
raise ArgumentError, "Block requires a content block" unless block_given?
66

7-
div class: "relative bg-white dark:bg-gray-800 shadow-md sm:rounded-lg my-3" do
7+
div class: tokens(
8+
theme_class(:block),
9+
"relative bg-surface dark:bg-surface-dark shadow-md sm:rounded-sm my-sm"
10+
) do
811
yield
912
end
1013
end

lib/plutonium/ui/breadcrumbs.rb

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ class Breadcrumbs < Plutonium::UI::Component::Base
66

77
def view_template
88
nav(
9-
class:
10-
"flex py-3 text-gray-700 mb-2",
9+
class: tokens(
10+
theme_class(:breadcrumbs),
11+
"flex py-sm text-gray-700 mb-sm"
12+
),
1113
aria_label: "Breadcrumb"
1214
) do
1315
ol(
1416
class:
15-
"inline-flex items-center space-x-1 md:space-x-2 rtl:space-x-reverse"
17+
"inline-flex items-center space-x-xs md:space-x-sm rtl:space-x-reverse"
1618
) do
1719
# Dashboard
1820
li(class: "inline-flex items-center") do
@@ -42,7 +44,7 @@ def view_template
4244
# Parent Resource
4345
li(class: "flex items-center") do
4446
svg(
45-
class: "rtl:rotate-180 block w-3 h-3 mx-1 text-gray-400",
47+
class: "rtl:rotate-180 block w-3 h-3 mx-xs text-gray-400",
4648
aria_hidden: "true",
4749
xmlns: "http://www.w3.org/2000/svg",
4850
fill: "none",
@@ -65,7 +67,7 @@ def view_template
6567
# Parent Itself
6668
li(class: "flex items-center") do
6769
svg(
68-
class: "rtl:rotate-180 block w-3 h-3 mx-1 text-gray-400",
70+
class: "rtl:rotate-180 block w-3 h-3 mx-xs text-gray-400",
6971
aria_hidden: "true",
7072
xmlns: "http://www.w3.org/2000/svg",
7173
fill: "none",
@@ -92,7 +94,7 @@ def view_template
9294
# Record Resource
9395
li(class: "flex items-center") do
9496
svg(
95-
class: "rtl:rotate-180 block w-3 h-3 mx-1 text-gray-400",
97+
class: "rtl:rotate-180 block w-3 h-3 mx-xs text-gray-400",
9698
aria_hidden: "true",
9799
xmlns: "http://www.w3.org/2000/svg",
98100
fill: "none",
@@ -117,7 +119,7 @@ def view_template
117119
if resource_record!.persisted? && action_name != "show"
118120
li(class: "flex items-center") do
119121
svg(
120-
class: "rtl:rotate-180 block w-3 h-3 mx-1 text-gray-400",
122+
class: "rtl:rotate-180 block w-3 h-3 mx-xs text-gray-400",
121123
aria_hidden: "true",
122124
xmlns: "http://www.w3.org/2000/svg",
123125
fill: "none",
@@ -142,7 +144,7 @@ def view_template
142144
# Trailing Caret
143145
li(class: "flex items-center") do
144146
svg(
145-
class: "rtl:rotate-180 block w-3 h-3 mx-1 text-gray-400",
147+
class: "rtl:rotate-180 block w-3 h-3 mx-xs text-gray-400",
146148
aria_hidden: "true",
147149
xmlns: "http://www.w3.org/2000/svg",
148150
fill: "none",

lib/plutonium/ui/color_mode_selector.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ module UI
88
class ColorModeSelector < Plutonium::UI::Component::Base
99
# Common CSS classes used across the component
1010
COMMON_CLASSES = {
11-
button: "inline-flex justify-center items-center p-2 text-gray-500 rounded cursor-pointer dark:hover:text-white dark:text-gray-200 hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors duration-200",
12-
icon: "w-5 h-5"
11+
button: "pu-color-mode-button inline-flex justify-center items-center p-2 text-gray-500 rounded cursor-pointer dark:hover:text-white dark:text-gray-200 hover:text-gray-900 hover:bg-interactive dark:hover:bg-interactive-dark transition-colors duration-200",
12+
icon: "pu-color-mode-icon w-5 h-5"
1313
}.freeze
1414

1515
# Available color modes with their associated icons and actions

lib/plutonium/ui/component/behaviour.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ module Behaviour
1010
include Kit
1111
include Tokens
1212

13+
# Generate custom CSS class for theming
14+
# @example
15+
# theme_class(:button) # => "pu-button"
16+
# theme_class(:button, variant: :table) # => "pu-button-table"
17+
def theme_class(component, variant: nil, element: nil)
18+
Theme.custom_class(component, variant:, element:)
19+
end
20+
1321
if Rails.env.development?
1422
def around_template(&)
1523
comment { "open:#{self.class.name}" }

lib/plutonium/ui/component/kit.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module Component
1515
# def view_template
1616
# PageHeader(title: "Dashboard")
1717
# TabList(items: tabs)
18-
# Panel(class: "mt-4") do
18+
# Panel(class: "mt-md") do
1919
# content
2020
# end
2121
# end
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
module Plutonium
4+
module UI
5+
module Component
6+
# Provides custom CSS class names for Plutonium UI components
7+
# to enable CSS-based theming without overriding components
8+
#
9+
# Users can customize via:
10+
# 1. Tailwind config (for ALL design tokens - radius, shadows, spacing, colors, etc.)
11+
# 2. CSS overrides using custom classes (e.g., .pu-button, .pu-card)
12+
#
13+
# @example Customizing via Tailwind config
14+
# // tailwind.config.js
15+
# module.exports = {
16+
# theme: {
17+
# extend: {
18+
# borderRadius: {
19+
# 'lg': '1rem', // Makes all rounded-sm bigger
20+
# },
21+
# boxShadow: {
22+
# 'md': '0 8px 16px rgba(0,0,0,0.1)', // Customize shadow-md
23+
# }
24+
# }
25+
# }
26+
# }
27+
#
28+
# @example Customizing specific components via CSS
29+
# .pu-button {
30+
# text-transform: uppercase;
31+
# }
32+
#
33+
# .pu-card {
34+
# border-radius: 0; /* Sharp corners only on cards */
35+
# }
36+
#
37+
module Theme
38+
# Custom class names for CSS targeting
39+
# Format: pu-{component}[-{variant}][-{element}]
40+
def self.custom_class(component, variant: nil, element: nil)
41+
parts = ["pu", component, variant, element].compact
42+
parts.join("-")
43+
end
44+
end
45+
end
46+
end
47+
end

lib/plutonium/ui/display/components/attachment.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ def render_thumbnail(attachment)
3434
return unless attachment.url.present?
3535

3636
div(
37-
class: "w-full aspect-square bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 transition-all duration-300",
37+
class: "w-full aspect-square bg-white border border-gray-200 rounded-sm shadow hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 transition-all duration-300",
3838
data: {attachment_preview_target: "thumbnail"}
3939
) do
4040
a(
4141
href: attachment.url,
42-
class: "block aspect-square overflow-hidden rounded-lg",
42+
class: "block aspect-square overflow-hidden rounded-sm",
4343
target: :blank,
4444
data: {attachment_preview_target: "thumbnailLink"}
4545
) do

lib/plutonium/ui/display/theme.rb

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ module Display
66
class Theme < Phlexi::Display::Theme
77
def self.theme
88
super.merge({
9-
base: "",
9+
base: "pu-display",
1010
value_wrapper: "max-h-[300px] overflow-y-auto",
11-
fields_wrapper: "p-6 grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-6 gap-y-10 grid-flow-row-dense",
12-
label: "text-base font-bold text-gray-500 dark:text-gray-400 mb-1",
13-
description: "text-sm text-gray-400 dark:text-gray-500",
14-
placeholder: "text-md text-gray-500 dark:text-gray-300 mb-1 italic",
15-
string: "text-md text-gray-900 dark:text-white mb-1 whitespace-pre-line",
16-
text: "text-md text-gray-900 dark:text-white mb-1 whitespace-pre-line",
17-
link: "text-primary-600 dark:text-primary-500 whitespace-pre-line",
18-
color: "flex items-center text-md text-gray-900 dark:text-white mb-1 whitespace-pre-line",
19-
color_indicator: "w-10 h-10 rounded-full mr-2", # max-h-fit
20-
email: "flex items-center text-md text-primary-600 dark:text-primary-500 mb-1 whitespace-pre-line",
21-
phone: "flex items-center text-md text-primary-600 dark:text-primary-500 mb-1 whitespace-pre-line",
22-
json: "text-sm text-gray-900 dark:text-white mb-1 whitespace-pre font-mono shadow-inner p-4",
23-
prefixed_icon: "w-8 h-8 mr-2",
24-
markdown: "format dark:format-invert format-primary",
25-
attachment_value_wrapper: "grid grid-cols-[repeat(auto-fill,minmax(0,180px))]",
11+
fields_wrapper: "pu-display-fields p-lg grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-lg gap-y-2xl grid-flow-row-dense",
12+
label: "pu-display-label text-base font-bold text-gray-500 dark:text-gray-400 mb-xs",
13+
description: "pu-display-description text-sm text-gray-400 dark:text-gray-500",
14+
placeholder: "pu-display-placeholder text-md text-gray-500 dark:text-gray-300 mb-xs italic",
15+
string: "pu-display-string text-md text-gray-900 dark:text-white mb-xs whitespace-pre-line",
16+
text: "pu-display-text text-md text-gray-900 dark:text-white mb-xs whitespace-pre-line",
17+
link: "pu-display-link text-primary-600 dark:text-primary-500 whitespace-pre-line",
18+
color: "pu-display-color flex items-center text-md text-gray-900 dark:text-white mb-xs whitespace-pre-line",
19+
color_indicator: "pu-display-color-indicator w-10 h-10 rounded-full mr-sm", # max-h-fit
20+
email: "pu-display-email flex items-center text-md text-primary-600 dark:text-primary-500 mb-xs whitespace-pre-line",
21+
phone: "pu-display-phone flex items-center text-md text-primary-600 dark:text-primary-500 mb-xs whitespace-pre-line",
22+
json: "pu-display-json text-sm text-gray-900 dark:text-white mb-xs whitespace-pre font-mono shadow-inner p-md",
23+
prefixed_icon: "pu-display-icon w-8 h-8 mr-sm",
24+
markdown: "pu-display-markdown format dark:format-invert format-primary",
25+
attachment_value_wrapper: "pu-display-attachments grid grid-cols-[repeat(auto-fill,minmax(0,180px))]",
2626
phlexi_render: :string
2727
})
2828
end

0 commit comments

Comments
 (0)