Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ PATH
servactory-web (0.1.0)
rails (>= 7.1, < 8.1)
servactory (>= 2.16.0.rc1)
view_component (>= 3.23)
zeitwerk (>= 2.6)

GEM
Expand Down Expand Up @@ -80,6 +81,8 @@ GEM
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
appraisal (2.5.0)
bundler
rake
Expand All @@ -89,6 +92,15 @@ GEM
benchmark (0.4.1)
bigdecimal (3.2.2)
builder (3.3.0)
capybara (3.40.0)
addressable
matrix
mini_mime (>= 0.1.3)
nokogiri (~> 1.11)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
crass (1.0.6)
Expand Down Expand Up @@ -119,6 +131,8 @@ GEM
net-pop
net-smtp
marcel (1.0.4)
matrix (0.4.3)
method_source (1.1.0)
mini_mime (1.1.5)
minitest (5.25.5)
mutex_m (0.3.0)
Expand Down Expand Up @@ -164,6 +178,7 @@ GEM
psych (5.2.6)
date
stringio
public_suffix (6.0.2)
racc (1.8.1)
rack (3.1.16)
rack-session (2.1.1)
Expand Down Expand Up @@ -224,6 +239,14 @@ GEM
rspec-mocks (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (8.0.1)
actionpack (>= 7.2)
activesupport (>= 7.2)
railties (>= 7.2)
rspec-core (~> 3.13)
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.4)
rubocop (1.77.0)
json (~> 2.3)
Expand Down Expand Up @@ -297,10 +320,16 @@ GEM
unicode-emoji (4.0.4)
uri (1.0.3)
useragent (0.16.11)
view_component (3.23.2)
activesupport (>= 5.2.0, < 8.1)
concurrent-ruby (~> 1)
method_source (~> 1.0)
websocket-driver (0.8.0)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.7.3)

PLATFORMS
Expand All @@ -315,10 +344,12 @@ PLATFORMS

DEPENDENCIES
appraisal (>= 2.5)
capybara (>= 3.40)
propshaft (>= 1.1)
rake (>= 13.2)
rbs (>= 3.8)
rspec (>= 3.13)
rspec-rails (>= 7.0)
servactory-rubocop (>= 0.9)
servactory-web!
sqlite3 (>= 2.1)
Expand Down
2 changes: 1 addition & 1 deletion app/assets/stylesheets/servactory/web/compiled.css

Large diffs are not rendered by default.

195 changes: 195 additions & 0 deletions app/components/servactory/web/ui_kit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Servactory Web UI Kit

The UI Kit is a set of reusable ViewComponent-based components for the Servactory Web interface. All components are built according to atomic design principles (atoms, molecules, organisms), are easy to extend, and cover the main UI needs of the project.

## Atomic Design

- **Atoms** — basic elements (buttons, icons, badges, links).
- **Molecules** — simple compositions of atoms (section headers, list items, containers).
- **Organisms** — complex blocks combining molecules and atoms (cards, lists, service tree, navigation).

## General Rules

- Each component consists of a Ruby class and a template (`.rb` + `.html.erb`).
- All components are located in `app/components/servactory/web/ui_kit` according to atomic design.
- **All components support the `class_name:` parameter for customizing with TailwindCSS utility classes.**
- **To pass standard HTML attributes, use the `options:` parameter (e.g., aria-label, data-*, target, etc.).**
- **Use only TailwindCSS 4.1 utility classes. Do not add custom CSS classes.**
- Do not use `options` unless you need to pass standard HTML attributes.

---

## Atoms

### IconComponent
SVG icon (file, folder, etc.).
```erb
<%= render IconComponent.new(name: :file, class_name: 'w-6 h-6 text-blue-500') %>
```
**Parameters:**
- `name:` — icon name (`:file`, `:folder`, `:inputs`, `:internals`, `:outputs`, `:actions`, `:code`, `:custom`)
- `class_name:` — TailwindCSS utility classes for the SVG element (required: always specify size and color)

### LinkComponent
Customizable link.
```erb
<%= render LinkComponent.new(href: '/docs', text: 'Docs', options: { class: 'text-blue-600', target: '_blank' }) %>
```
**Parameters:**
- `href:` — URL
- `text:` — link text
- `options:` — standard HTML attributes and classes

### BadgeComponent
Badge with text, can be used for required/optional indicators.
```erb
<%= render BadgeComponent.new(text: 'Username', class_name: 'mb-2') %>
<%= render BadgeComponent.new(text: 'required', class_name: 'text-xs bg-gray-100 px-2 py-1 rounded text-gray-600') %>
```
**Parameters:**
- `text:` — badge text
- `class_name:` — TailwindCSS utility classes

### CopyButtonComponent
Button for copying code.
```erb
<%= render CopyButtonComponent.new(code: 'def foo; end') %>
```

### CardHeaderTextComponent
Заголовок для карточек и секций.
```erb
<%= render CardHeaderTextComponent.new(text: 'My Title', class_name: 'mb-2') %>
```
**Parameters:**
- `text:` — заголовок
- `class_name:` — TailwindCSS utility classes
- `options:` — стандартные HTML-атрибуты

### EmptyStateComponent
Empty state for lists.
```erb
<%= render EmptyStateComponent.new(message: 'No data') %>
```
**Parameters:**
- `message:` — message to display
- `class_name:` — TailwindCSS utility classes
- `options:` — standard HTML attributes

---

## Molecules

### SectionHeaderComponent
Section header with icon.
```erb
<%= render SectionHeaderComponent.new(title: 'Inputs', icon_name: :file, class_name: 'text-blue-600') %>
```
**Parameters:**
- `title:` — header text
- `icon_name:` — icon name (see IconComponent)
- `class_name:` — utility classes
- `options:` — HTML attributes

### AttributeItemComponent
Attribute list item (name, required/optional, description).
```erb
<%= render AttributeItemComponent.new(name: 'user_id', border_class: 'border-blue-500', text_class: 'text-blue-700', bg_class: 'bg-blue-50', attribute: attr_obj) do %>
...
<% end %>
```
**Parameters:**
- `name:` — attribute name
- `border_class:` — utility class for left border
- `text_class:` — utility class for text
- `bg_class:` — utility class for background
- `attribute:` — attribute object (determines required)
- `class_name:`, `options:`

### CardBodyComponent / CardBodyContainerComponent / CardContainerComponent
Internal containers for cards, support customization via class_name and options.

### CardHeaderComponent
Card header, supports customization.

---

## Organisms

### CardComponent
Universal container for sections/content.
```erb
<%= render CardComponent.new(class_name: 'shadow-lg') do |card| %>
<% card.with_header do %>
...
<% end %>
...content...
<% end %>
```
**Parameters:**
- `class_name:`, `options:`
- `header` slot for the header

### SectionCardComponent
Section card with header, icon, and attribute list (inputs, outputs, actions, etc.). Composes CardComponent, SectionHeaderComponent, AttributeListComponent.
```erb
<%= render SectionCardComponent.new(title: 'Inputs', items: {...}, border_class: 'border-blue-500', text_class: 'text-blue-700', bg_class: 'bg-blue-50', icon_name: :inputs, empty_message: 'No input attributes') %>
```
**Parameters:**
- `title:`, `items:`, `border_class:`, `text_class:`, `bg_class:`, `icon_name:`, `empty_message:`, `class_name:`, `options:`

### AttributeListComponent
List of attributes (inputs, outputs, internals, actions).
```erb
<%= render AttributeListComponent.new(items: {...}, border_class: 'border-blue-500', text_class: 'text-blue-700', bg_class: 'bg-blue-50', empty_message: 'No attributes') %>
```

### CodeBlockComponent
Block with source code and copy button.
```erb
<%= render CodeBlockComponent.new(code: 'def foo; end', language: 'ruby', copy_button: true) %>
```
**Parameters:**
- `code:`, `language:`, `copy_button:`

### TreeComponent / TreeNodeComponent
Service tree (service navigation).
```erb
<%= render TreeComponent.new(nodes: @services_tree) %>
```

### NavbarComponent
Top navigation bar.
```erb
<%= render NavbarComponent.new(app_name: 'MyApp', app_url: '/', documentation_url: '/docs', github_url: 'https://github.com/...') %>
```

### FooterComponent
Site footer.
```erb
<%= render FooterComponent.new(year: 2024, documentation_url: '/docs', github_url: 'https://github.com/...', version: '1.0.0', release_url: '/releases/1.0.0') %>
```

### PageHeaderComponent
Page header with description.
```erb
<%= render PageHeaderComponent.new(title: 'Title', description: 'Desc') %>
```

---

## Best Practices
- Use components for any repeated markup.
- For complex sections, compose components (e.g., Card + SectionHeader).
- Use IconComponent for SVG icons only.
- Always use LinkComponent for links for consistency and accessibility.
- Use only the `class_name:` parameter for custom utility classes.
- Use only the `options:` parameter for standard HTML attributes.
- Do not add custom CSS classes.
- Follow atomic design when adding new components.

## How to add a new component
1. Create a Ruby class and template in the appropriate folder (atoms, molecules, organisms).
2. Use the ViewComponent API (`< ComponentName < ViewComponent::Base`).
3. Add a description and example to this README.
4. All testing must be done using RSpec.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<span class="block text-sm font-medium text-gray-700 <%= @class_name %>">
<%= @text %>
</span>
17 changes: 17 additions & 0 deletions app/components/servactory/web/ui_kit/atoms/badge_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module Servactory
module Web
module UiKit
module Atoms
class BadgeComponent < ViewComponent::Base
def initialize(text:, class_name: nil)
super()
@text = text
@class_name = class_name
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h3 class="text-lg font-semibold text-gray-900 <%= @class_name %> <%= @options[:class] %>"><%= @text %></h3>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Servactory
module Web
module UiKit
module Atoms
class CardHeaderTextComponent < ViewComponent::Base
def initialize(text:, class_name: nil, options: {})
super()
@text = text
@class_name = class_name
@options = options
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<button type="button" class="text-sm text-gray-600 hover:text-gray-900 transition-colors flex items-center gap-1" onclick="navigator.clipboard.writeText('<%= j @code %>')">
<%= render Servactory::Web::UiKit::Atoms::IconComponent.new(name: :copy, class_name: 'w-4 h-4') %>
Copy
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module Servactory
module Web
module UiKit
module Atoms
class CopyButtonComponent < ViewComponent::Base
def initialize(code:)
super()
@code = code
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="p-8 text-center text-gray-500 text-sm <%= @class_name %> <%= @options[:class] %>">
<%= @message %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Servactory
module Web
module UiKit
module Atoms
class EmptyStateComponent < ViewComponent::Base
include Servactory::Web::UiKit::Concerns::ComponentOptions
def initialize(message:, class_name: nil, options: {})
super()
@message = message
initialize_component_options(class_name:, options:)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= raw svg %>
Loading