Skip to content

Commit 8d8fa3f

Browse files
committed
Add atomic design plan
1 parent 0dc8e8a commit 8d8fa3f

File tree

1 file changed

+360
-0
lines changed

1 file changed

+360
-0
lines changed
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
# Atomic Design Implementation Plan for Rails Frontend
2+
3+
This document outlines a phased approach to implementing Atomic Design principles in PropertyWebBuilder's Rails-rendered frontend (B-themes).
4+
5+
## Goals
6+
7+
1. **Improve reusability** - Share components across all B-themes
8+
2. **Reduce duplication** - Extract common patterns into reusable partials
9+
3. **Enhance maintainability** - Clear hierarchy makes code easier to navigate
10+
4. **Enable isolated testing** - Smaller units are easier to test
11+
5. **Accelerate theme development** - New themes only override CSS, not structure
12+
13+
---
14+
15+
## Phase 1: Directory Structure Setup
16+
17+
### Create Atomic Directories
18+
19+
```
20+
app/views/shared/
21+
├── atoms/ # Smallest UI primitives
22+
├── molecules/ # Simple combinations of atoms
23+
└── organisms/ # Complex, self-contained components
24+
```
25+
26+
### Files to Create
27+
28+
| Directory | Purpose |
29+
|-----------|---------|
30+
| `shared/atoms/` | Buttons, badges, icons, form inputs, links |
31+
| `shared/molecules/` | Form groups, price displays, nav items, stat cards |
32+
| `shared/organisms/` | Property cards, search filters, testimonial cards |
33+
34+
---
35+
36+
## Phase 2: Extract Atoms
37+
38+
### Priority Atoms to Create
39+
40+
#### 2.1 Button (`_button.html.erb`)
41+
42+
```erb
43+
<%# app/views/shared/atoms/_button.html.erb %>
44+
<%# Usage: render "shared/atoms/button", text: "Submit", variant: :primary, size: :md %>
45+
<%
46+
variant ||= :primary
47+
size ||= :md
48+
type ||= :button
49+
disabled ||= false
50+
51+
base_classes = "pwb-btn"
52+
variant_classes = {
53+
primary: "pwb-btn--primary",
54+
secondary: "pwb-btn--secondary",
55+
outline: "pwb-btn--outline",
56+
ghost: "pwb-btn--ghost"
57+
}
58+
size_classes = {
59+
sm: "pwb-btn--sm",
60+
md: "pwb-btn--md",
61+
lg: "pwb-btn--lg"
62+
}
63+
64+
classes = [base_classes, variant_classes[variant], size_classes[size], local_assigns[:class]].compact.join(" ")
65+
%>
66+
<button type="<%= type %>" class="<%= classes %>" <%= "disabled" if disabled %>>
67+
<%= text %>
68+
</button>
69+
```
70+
71+
#### 2.2 Badge (`_badge.html.erb`)
72+
73+
```erb
74+
<%# app/views/shared/atoms/_badge.html.erb %>
75+
<%
76+
variant ||= :default
77+
classes = "pwb-badge pwb-badge--#{variant} #{local_assigns[:class]}"
78+
%>
79+
<span class="<%= classes %>"><%= text %></span>
80+
```
81+
82+
#### 2.3 Icon (`_icon.html.erb`)
83+
84+
```erb
85+
<%# app/views/shared/atoms/_icon.html.erb %>
86+
<%# Wraps existing icon helper with consistent classes %>
87+
<%= icon(name, class: "pwb-icon pwb-icon--#{size || 'md'} #{local_assigns[:class]}") %>
88+
```
89+
90+
#### 2.4 Form Input (`_input.html.erb`)
91+
92+
```erb
93+
<%# app/views/shared/atoms/_input.html.erb %>
94+
<%
95+
type ||= :text
96+
required ||= false
97+
classes = "pwb-input #{local_assigns[:class]}"
98+
%>
99+
<input
100+
type="<%= type %>"
101+
name="<%= name %>"
102+
id="<%= id || name %>"
103+
value="<%= value %>"
104+
placeholder="<%= placeholder %>"
105+
class="<%= classes %>"
106+
<%= "required" if required %>
107+
>
108+
```
109+
110+
---
111+
112+
## Phase 3: Extract Molecules
113+
114+
### Priority Molecules to Create
115+
116+
#### 3.1 Form Group (`_form_group.html.erb`)
117+
118+
```erb
119+
<%# app/views/shared/molecules/_form_group.html.erb %>
120+
<div class="pwb-form-group">
121+
<label for="<%= input_id %>" class="pwb-form-group__label">
122+
<%= label_text %>
123+
<% if required %><span class="pwb-form-group__required">*</span><% end %>
124+
</label>
125+
<%= yield %>
126+
<% if error.present? %>
127+
<span class="pwb-form-group__error"><%= error %></span>
128+
<% end %>
129+
</div>
130+
```
131+
132+
#### 3.2 Price Display (`_price_display.html.erb`)
133+
134+
```erb
135+
<%# app/views/shared/molecules/_price_display.html.erb %>
136+
<div class="pwb-price-display">
137+
<span class="pwb-price-display__value"><%= formatted_price %></span>
138+
<% if rental %>
139+
<span class="pwb-price-display__suffix">/<%= I18n.t('common.month') %></span>
140+
<% end %>
141+
</div>
142+
```
143+
144+
#### 3.3 Property Stats (`_property_stats.html.erb`)
145+
146+
```erb
147+
<%# app/views/shared/molecules/_property_stats.html.erb %>
148+
<div class="pwb-prop-stats">
149+
<span class="pwb-prop-stats__item">
150+
<%= render "shared/atoms/icon", name: "bed", size: "sm" %>
151+
<%= bedrooms %> <%= I18n.t('properties.beds') %>
152+
</span>
153+
<span class="pwb-prop-stats__item">
154+
<%= render "shared/atoms/icon", name: "bath", size: "sm" %>
155+
<%= bathrooms %> <%= I18n.t('properties.baths') %>
156+
</span>
157+
<% if garages.present? && garages > 0 %>
158+
<span class="pwb-prop-stats__item">
159+
<%= render "shared/atoms/icon", name: "car", size: "sm" %>
160+
<%= garages %> <%= I18n.t('properties.garage') %>
161+
</span>
162+
<% end %>
163+
</div>
164+
```
165+
166+
#### 3.4 Nav Link (`_nav_link.html.erb`)
167+
168+
```erb
169+
<%# app/views/shared/molecules/_nav_link.html.erb %>
170+
<li class="pwb-nav__item <%= 'pwb-nav__item--active' if active %>">
171+
<%= link_to path, class: "pwb-nav__link", target: target do %>
172+
<% if icon.present? %>
173+
<%= render "shared/atoms/icon", name: icon, size: "sm" %>
174+
<% end %>
175+
<%= title %>
176+
<% end %>
177+
</li>
178+
```
179+
180+
---
181+
182+
## Phase 4: Extract Organisms
183+
184+
### Priority Organisms to Create
185+
186+
#### 4.1 Property Card (`_property_card.html.erb`)
187+
188+
Extract from existing property listing views:
189+
190+
```erb
191+
<%# app/views/shared/organisms/_property_card.html.erb %>
192+
<article class="pwb-prop-card" data-property-id="<%= property.id %>">
193+
<a href="<%= property_path(property) %>" class="pwb-prop-card__link">
194+
<div class="pwb-prop-card__image">
195+
<%= image_tag property.primary_photo_url, alt: property.title, loading: "lazy" %>
196+
<% if property.highlighted? %>
197+
<%= render "shared/atoms/badge", text: I18n.t('properties.featured'), variant: :primary %>
198+
<% end %>
199+
<div class="pwb-prop-card__price">
200+
<%= render "shared/molecules/price_display",
201+
formatted_price: property.formatted_price,
202+
rental: property.for_rent? && !property.for_sale? %>
203+
</div>
204+
</div>
205+
<div class="pwb-prop-card__body">
206+
<h3 class="pwb-prop-card__title"><%= property.title %></h3>
207+
<%= render "shared/molecules/property_stats",
208+
bedrooms: property.count_bedrooms,
209+
bathrooms: property.count_bathrooms,
210+
garages: property.count_garages %>
211+
</div>
212+
</a>
213+
</article>
214+
```
215+
216+
#### 4.2 Testimonial Card (`_testimonial_card.html.erb`)
217+
218+
```erb
219+
<%# app/views/shared/organisms/_testimonial_card.html.erb %>
220+
<blockquote class="pwb-testimonial">
221+
<div class="pwb-testimonial__content">
222+
<p class="pwb-testimonial__quote"><%= testimonial.content %></p>
223+
</div>
224+
<footer class="pwb-testimonial__footer">
225+
<% if testimonial.photo_url.present? %>
226+
<%= image_tag testimonial.photo_url, alt: testimonial.name, class: "pwb-testimonial__avatar" %>
227+
<% end %>
228+
<div class="pwb-testimonial__author">
229+
<cite class="pwb-testimonial__name"><%= testimonial.name %></cite>
230+
<% if testimonial.role.present? %>
231+
<span class="pwb-testimonial__role"><%= testimonial.role %></span>
232+
<% end %>
233+
</div>
234+
</footer>
235+
</blockquote>
236+
```
237+
238+
#### 4.3 Search Filters (`_search_filters.html.erb`)
239+
240+
Extract from existing search form implementation.
241+
242+
---
243+
244+
## Phase 5: Refactor Existing Partials
245+
246+
### Files to Migrate
247+
248+
| Current Location | Target | Priority |
249+
|------------------|--------|----------|
250+
| `pwb/_header.html.erb` | Keep as organism, extract molecules | High |
251+
| `pwb/_footer.html.erb` | Keep as organism, extract molecules | High |
252+
| Property listing cards | `shared/organisms/_property_card.html.erb` | High |
253+
| `page_parts/heroes/*` | Keep, use atoms/molecules internally | Medium |
254+
| `page_parts/features/*` | Keep, use atoms/molecules internally | Medium |
255+
| `page_parts/testimonials/*` | Keep, use atoms/molecules internally | Medium |
256+
257+
---
258+
259+
## Phase 6: CSS Organization
260+
261+
### BEM Class Naming
262+
263+
All atomic components use BEM with `pwb-` prefix:
264+
265+
```css
266+
/* Atoms */
267+
.pwb-btn { }
268+
.pwb-btn--primary { }
269+
.pwb-btn--lg { }
270+
271+
/* Molecules */
272+
.pwb-form-group { }
273+
.pwb-form-group__label { }
274+
.pwb-form-group__error { }
275+
276+
/* Organisms */
277+
.pwb-prop-card { }
278+
.pwb-prop-card__image { }
279+
.pwb-prop-card__title { }
280+
```
281+
282+
### Create Component CSS Files
283+
284+
```
285+
app/views/pwb/custom_css/
286+
├── atoms/
287+
│ ├── _buttons.css.erb
288+
│ ├── _badges.css.erb
289+
│ └── _icons.css.erb
290+
├── molecules/
291+
│ ├── _form_group.css.erb
292+
│ ├── _price_display.css.erb
293+
│ └── _property_stats.css.erb
294+
└── organisms/
295+
├── _property_card.css.erb
296+
└── _testimonial.css.erb
297+
```
298+
299+
---
300+
301+
## Phase 7: Testing Strategy
302+
303+
### ViewComponent Migration (Optional)
304+
305+
For complex organisms, consider migrating to [ViewComponent](https://viewcomponent.org/):
306+
307+
```ruby
308+
# app/components/property_card_component.rb
309+
class PropertyCardComponent < ViewComponent::Base
310+
def initialize(property:, show_badge: true)
311+
@property = property
312+
@show_badge = show_badge
313+
end
314+
end
315+
```
316+
317+
### Partial Testing
318+
319+
```ruby
320+
# spec/views/shared/atoms/button_spec.rb
321+
RSpec.describe "shared/atoms/_button.html.erb" do
322+
it "renders primary button" do
323+
render partial: "shared/atoms/button", locals: { text: "Click", variant: :primary }
324+
expect(rendered).to have_css(".pwb-btn.pwb-btn--primary", text: "Click")
325+
end
326+
end
327+
```
328+
329+
---
330+
331+
## Implementation Timeline
332+
333+
| Phase | Estimated Effort | Dependencies |
334+
|-------|------------------|--------------|
335+
| Phase 1: Directory Setup | 1 hour | None |
336+
| Phase 2: Extract Atoms | 4-6 hours | Phase 1 |
337+
| Phase 3: Extract Molecules | 4-6 hours | Phase 2 |
338+
| Phase 4: Extract Organisms | 8-12 hours | Phase 3 |
339+
| Phase 5: Refactor Existing | 12-16 hours | Phase 4 |
340+
| Phase 6: CSS Organization | 4-6 hours | Phase 4 |
341+
| Phase 7: Testing | 8-12 hours | All phases |
342+
343+
**Total Estimated Effort**: 40-60 hours (spread over multiple sprints)
344+
345+
---
346+
347+
## Success Metrics
348+
349+
- [ ] All B-themes share common atoms/molecules
350+
- [ ] New theme creation requires only CSS changes
351+
- [ ] 80%+ code reuse across themes
352+
- [ ] Component preview/documentation available (Lookbook or similar)
353+
- [ ] Reduced bug reports related to UI inconsistencies
354+
355+
---
356+
357+
## Related Documents
358+
359+
- [Design Tokens](./DESIGN_TOKENS.md) - Token values used by atomic components
360+
- [Frontend Standards](../FRONTEND_STANDARDS.md) - BEM naming conventions

0 commit comments

Comments
 (0)