frozen_rails 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -2
- data/LICENSE.txt +1 -1
- data/README.md +46 -82
- data/Rakefile +20 -0
- data/lib/frozen_rails/appendable.rb +9 -2
- data/lib/frozen_rails/copyable.rb +7 -2
- data/lib/frozen_rails/generator.rb +0 -2
- data/lib/frozen_rails/taggable.rb +3 -2
- data/lib/frozen_rails/version.rb +1 -3
- data/lib/frozen_rails.rb +0 -2
- data/lib/generators/frozen/db_generator.rb +2 -5
- data/lib/generators/frozen/md_generator.rb +6 -3
- data/lib/generators/frozen/rails_generator.rb +6 -14
- data/lib/generators/frozen/seo_generator.rb +5 -7
- data/lib/generators/frozen/ssg_generator.rb +0 -2
- data/lib/generators/frozen/templates/db/database.yml +15 -2
- data/lib/generators/frozen/templates/db/initializers/skip_controller_tests_and_helpers.rb +9 -0
- data/lib/generators/frozen/templates/db/lib/generators/avo/resource/resource_generator.rb +1 -1
- data/lib/generators/frozen/templates/db/lib/templates/active_record/model/model.rb.tt +12 -1
- data/lib/generators/frozen/templates/db/lib/templates/erb/scaffold/index.html.erb.tt +14 -8
- data/lib/generators/frozen/templates/db/lib/templates/erb/scaffold/partial.html.erb.tt +2 -2
- data/lib/generators/frozen/templates/db/lib/templates/erb/scaffold/show.html.erb.tt +8 -7
- data/lib/generators/frozen/templates/md/assets/application.css +13 -0
- data/lib/generators/frozen/templates/md/assets/rouge/README.md +7 -0
- data/lib/generators/frozen/templates/md/assets/rouge/revert.css +11 -0
- data/lib/generators/frozen/templates/md/views/categories/index.html.erb +5 -3
- data/lib/generators/frozen/templates/md/views/categories/show.html.erb +5 -9
- data/lib/generators/frozen/templates/md/views/pages/show.html.erb +6 -3
- data/lib/generators/frozen/templates/ssg/.gitlab-ci.yml +34 -0
- data/lib/generators/frozen/templates/ui/application.html.erb +27 -0
- data/lib/generators/frozen/templates/ui/application_component.rb +2 -0
- data/lib/generators/frozen/templates/ui/assets/application.js +4 -0
- data/lib/generators/frozen/templates/ui/assets/builds/.keep +0 -0
- data/lib/generators/frozen/templates/ui/assets/components/index.js +2 -0
- data/lib/generators/frozen/templates/ui/assets/fonts/.keep +0 -0
- data/lib/generators/frozen/templates/ui/assets/images/.keep +0 -0
- data/lib/generators/frozen/templates/ui/assets/images/pages/.keep +0 -0
- data/lib/generators/frozen/templates/ui/assets/jasmine/boot.js +6 -0
- data/lib/generators/frozen/templates/ui/assets/jasmine/support/fixtures.js +24 -0
- data/lib/generators/frozen/templates/ui/assets/jasmine/support/jasmine-dom.js +1 -0
- data/lib/generators/frozen/templates/ui/assets/jasmine/support/unpoly-test-config.js +7 -0
- data/lib/generators/frozen/templates/ui/assets/jasmine/support.js +1 -0
- data/lib/generators/frozen/templates/ui/assets/jasmine.js +5 -0
- data/lib/generators/frozen/templates/ui/assets/rouge/index.js +2 -0
- data/lib/generators/frozen/templates/ui/assets/static.js +5 -0
- data/lib/generators/frozen/templates/ui/assets/theme/README.md +11 -0
- data/lib/generators/frozen/templates/ui/assets/theme/base.css +27 -0
- data/lib/generators/frozen/templates/ui/assets/theme/code.css +29 -0
- data/lib/generators/frozen/templates/ui/assets/theme/forms.css +160 -0
- data/lib/generators/frozen/templates/ui/assets/theme/index.js +2 -0
- data/lib/generators/frozen/templates/ui/assets/theme/links.css +8 -0
- data/lib/generators/frozen/templates/ui/assets/theme/misc.css +150 -0
- data/lib/generators/frozen/templates/ui/assets/theme/parts.css +8 -0
- data/lib/generators/frozen/templates/ui/assets/theme/print.css +53 -0
- data/lib/generators/frozen/templates/ui/assets/theme/range.css +87 -0
- data/lib/generators/frozen/templates/ui/assets/theme/select-arrow.svg +4 -0
- data/lib/generators/frozen/templates/ui/assets/theme/typography.css +89 -0
- data/lib/generators/frozen/templates/ui/assets/theme/variables.css +49 -0
- data/lib/generators/frozen/templates/ui/assets/unpoly/index.js +7 -0
- data/lib/generators/frozen/templates/ui/demo_component/counter_component.css +18 -0
- data/lib/generators/frozen/templates/ui/demo_component/counter_component.html.erb +6 -0
- data/lib/generators/frozen/templates/ui/demo_component/counter_component.js +24 -0
- data/lib/generators/frozen/templates/ui/demo_component/counter_component.rb +6 -0
- data/lib/generators/frozen/templates/ui/demo_component/counter_component.yml +5 -0
- data/lib/generators/frozen/templates/ui/demo_component_tests/counter_component_preview.rb +54 -0
- data/lib/generators/frozen/templates/ui/demo_component_tests/counter_component_spec.js +25 -0
- data/lib/generators/frozen/templates/ui/demo_component_tests/counter_component_test.rb +40 -0
- data/lib/generators/frozen/templates/ui/demo_component_tests/docs/styleguide.md.erb +30 -0
- data/lib/generators/frozen/templates/ui/demo_component_tests/manual_test_component_spec.js +14 -0
- data/lib/generators/frozen/templates/ui/esbuild.config.js +79 -0
- data/lib/generators/frozen/templates/ui/jasmine/controllers/jasmine_controller.rb +12 -0
- data/lib/generators/frozen/templates/ui/jasmine/views/jasmine/index.html.erb +19 -0
- data/lib/generators/frozen/templates/ui/jasmine/views/layouts/jasmine.html.erb +17 -0
- data/lib/generators/frozen/templates/ui/lib/generators/component/component_generator.rb +72 -0
- data/lib/generators/frozen/templates/ui/lib/tasks/assets.rake +14 -0
- data/lib/generators/frozen/templates/ui/lib/templates/view_component/component/component.rb.tt +19 -0
- data/lib/generators/frozen/templates/ui/lib/templates/view_component/frozen_extensions/bem_stylesheet.css.tt +3 -0
- data/lib/generators/frozen/templates/ui/lib/templates/view_component/frozen_extensions/erb_template.html.erb.tt +5 -0
- data/lib/generators/frozen/templates/ui/lib/templates/view_component/frozen_extensions/jasmine_spec.js.tt +15 -0
- data/lib/generators/frozen/templates/ui/lib/templates/view_component/frozen_extensions/unpoly_compiler.js.tt +5 -0
- data/lib/generators/frozen/templates/ui/lib/templates/view_component/frozen_extensions/view_spec.rb.tt +21 -0
- data/lib/generators/frozen/templates/ui/lib/templates/view_component/preview/component_preview.rb.tt +15 -0
- data/lib/generators/frozen/templates/ui/lookbook/lookbook_helper.rb +38 -0
- data/lib/generators/frozen/ui_generator.rb +135 -41
- metadata +68 -10
- data/lib/generators/frozen/templates/ui/cable.yml +0 -5
- data/lib/generators/frozen/templates/ui/hotkey_controller.js +0 -9
- /data/lib/generators/frozen/templates/db/initializers/{readonly_routes.rb → skip_writing_routes.rb} +0 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="62.5" width="116.9">
|
|
3
|
+
<path d="M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
h1 {
|
|
2
|
+
font-size: 2.2em;
|
|
3
|
+
margin-top: 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
h1,
|
|
7
|
+
h2,
|
|
8
|
+
h3,
|
|
9
|
+
h4,
|
|
10
|
+
h5,
|
|
11
|
+
h6 {
|
|
12
|
+
margin-bottom: 12px;
|
|
13
|
+
margin-top: 24px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
h1,
|
|
17
|
+
h2,
|
|
18
|
+
h3,
|
|
19
|
+
h4,
|
|
20
|
+
h5,
|
|
21
|
+
h6,
|
|
22
|
+
strong {
|
|
23
|
+
color: var(--text-bright);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
h1,
|
|
27
|
+
h2,
|
|
28
|
+
h3,
|
|
29
|
+
h4,
|
|
30
|
+
h5,
|
|
31
|
+
h6,
|
|
32
|
+
b,
|
|
33
|
+
strong,
|
|
34
|
+
th {
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
q::before {
|
|
39
|
+
content: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
q::after {
|
|
43
|
+
content: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
blockquote,
|
|
47
|
+
q {
|
|
48
|
+
border-left: 4px solid var(--focus);
|
|
49
|
+
margin: 1.5em 0;
|
|
50
|
+
padding: 0.5em 1em;
|
|
51
|
+
font-style: italic;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
blockquote > footer {
|
|
55
|
+
font-style: normal;
|
|
56
|
+
border: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
blockquote cite {
|
|
60
|
+
font-style: normal;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
address {
|
|
64
|
+
font-style: normal;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
a[href^='mailto\:']::before {
|
|
68
|
+
content: '📧 ';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
a[href^='tel\:']::before {
|
|
72
|
+
content: '📞 ';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
a[href^='sms\:']::before {
|
|
76
|
+
content: '💬 ';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
mark {
|
|
80
|
+
background-color: var(--highlight);
|
|
81
|
+
border-radius: 2px;
|
|
82
|
+
padding: 0 2px 0 2px;
|
|
83
|
+
color: #000;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
a > code,
|
|
87
|
+
a > strong {
|
|
88
|
+
color: inherit;
|
|
89
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--background-body: #fff;
|
|
3
|
+
--background: #efefef;
|
|
4
|
+
--background-alt: #f7f7f7;
|
|
5
|
+
--selection: #9e9e9e;
|
|
6
|
+
--text-main: #363636;
|
|
7
|
+
--text-bright: #000;
|
|
8
|
+
--text-muted: #70777f;
|
|
9
|
+
--links: #0076d1;
|
|
10
|
+
--focus: #0096bfab;
|
|
11
|
+
--border: #dbdbdb;
|
|
12
|
+
--code: #000;
|
|
13
|
+
--animation-duration: 0.1s;
|
|
14
|
+
--button-base: #d0cfcf;
|
|
15
|
+
--button-hover: #9b9b9b;
|
|
16
|
+
--scrollbar-thumb: rgb(170, 170, 170);
|
|
17
|
+
--scrollbar-thumb-hover: var(--button-hover);
|
|
18
|
+
--form-placeholder: #949494;
|
|
19
|
+
--form-text: #1d1d1d;
|
|
20
|
+
--variable: #39a33c;
|
|
21
|
+
--highlight: #ff0;
|
|
22
|
+
--select-arrow: url("select-arrow.svg");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@media (prefers-color-scheme: dark) {
|
|
26
|
+
:root {
|
|
27
|
+
--background-body: #202b38;
|
|
28
|
+
--background: #161f27;
|
|
29
|
+
--background-alt: #1a242f;
|
|
30
|
+
--selection: #1c76c5;
|
|
31
|
+
--text-main: #dbdbdb;
|
|
32
|
+
--text-bright: #fff;
|
|
33
|
+
--text-muted: #a9b1ba;
|
|
34
|
+
--links: #41adff;
|
|
35
|
+
--focus: #0096bfab;
|
|
36
|
+
--border: #526980;
|
|
37
|
+
--code: #ffbe85;
|
|
38
|
+
--animation-duration: 0.1s;
|
|
39
|
+
--button-base: #0c151c;
|
|
40
|
+
--button-hover: #040a0f;
|
|
41
|
+
--scrollbar-thumb: var(--button-hover);
|
|
42
|
+
--scrollbar-thumb-hover: rgb(0, 0, 0);
|
|
43
|
+
--form-placeholder: #a9a9a9;
|
|
44
|
+
--form-text: #fff;
|
|
45
|
+
--variable: #d941e2;
|
|
46
|
+
--highlight: #efdb43;
|
|
47
|
+
--select-arrow: url("select-arrow.svg");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.counter {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: 0.5rem;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.counter button {
|
|
8
|
+
min-width: 2.25rem;
|
|
9
|
+
min-height: 2.25rem;
|
|
10
|
+
padding: 0.35rem 0.75rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.counter span {
|
|
14
|
+
min-width: 2rem;
|
|
15
|
+
display: inline-block;
|
|
16
|
+
text-align: center;
|
|
17
|
+
font-weight: 600;
|
|
18
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<div class="counter" data-compiler="counter" up-data="<%= { initialValue: @initial_value }.to_json %>">
|
|
2
|
+
<% if @label %><h2><%= t('.label') %></h2><% end %>
|
|
3
|
+
<button type="button" data-element="counter:decrement">−</button>
|
|
4
|
+
<span data-element="counter:value"><%= @initial_value %></span>
|
|
5
|
+
<button type="button" data-element="counter:increment">+</button>
|
|
6
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
up.compiler('[data-compiler="counter"]', (element, { initialValue }) => {
|
|
2
|
+
const display = element.querySelector('[data-element="counter:value"]')
|
|
3
|
+
const decrementButton = element.querySelector('[data-element="counter:decrement"]')
|
|
4
|
+
const incrementButton = element.querySelector('[data-element="counter:increment"]')
|
|
5
|
+
|
|
6
|
+
let value = Number(initialValue || 0)
|
|
7
|
+
|
|
8
|
+
const updateDisplay = () => {
|
|
9
|
+
display.textContent = value
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const changeValue = (delta) => {
|
|
13
|
+
value += delta
|
|
14
|
+
updateDisplay()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const destructors = [
|
|
18
|
+
up.on(decrementButton, 'click', () => changeValue(-1)),
|
|
19
|
+
up.on(incrementButton, 'click', () => changeValue(1))
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
updateDisplay()
|
|
23
|
+
return destructors
|
|
24
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class CounterComponentPreview < ViewComponent::Preview
|
|
2
|
+
# A counter with custom initial value
|
|
3
|
+
# ---
|
|
4
|
+
# You probably don't want to use this component for real,
|
|
5
|
+
# because it doesn't persist the counter value anywhere.
|
|
6
|
+
#
|
|
7
|
+
# @label Custom
|
|
8
|
+
# @param initial_value [Integer]
|
|
9
|
+
def custom(initial_value: 0)
|
|
10
|
+
render CounterComponent.new(initial_value: initial_value)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @!group Initial Values
|
|
14
|
+
|
|
15
|
+
# @label Default
|
|
16
|
+
def zero
|
|
17
|
+
render CounterComponent.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @label Manually set
|
|
21
|
+
def forty_two
|
|
22
|
+
render CounterComponent.new(initial_value: 42)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @!endgroup
|
|
26
|
+
|
|
27
|
+
# @hidden
|
|
28
|
+
def negative_one
|
|
29
|
+
render CounterComponent.new(initial_value: -1)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @!group I18n
|
|
33
|
+
|
|
34
|
+
# @label English
|
|
35
|
+
def english
|
|
36
|
+
I18n.with_locale(:en) do
|
|
37
|
+
render CounterComponent.new(label: true)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Caution
|
|
42
|
+
# ---
|
|
43
|
+
# Getting too sophisticated within a preview isn't supported:
|
|
44
|
+
# - [https://github.com/lookbook-hq/lookbook/issues/793](https://github.com/lookbook-hq/lookbook/issues/793)
|
|
45
|
+
#
|
|
46
|
+
# @label German [Lookbook BUG, see Notes tab]
|
|
47
|
+
def german
|
|
48
|
+
I18n.with_locale(:de) do
|
|
49
|
+
render CounterComponent.new(label: true)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @!endgroup
|
|
54
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
describe('CounterComponent', () => {
|
|
2
|
+
let component, decrement, increment, value
|
|
3
|
+
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
const container = loadPreviewScenario('counter', 'zero')
|
|
6
|
+
component = container.querySelector('.counter')
|
|
7
|
+
decrement = component.querySelector('[data-element="counter:decrement"]')
|
|
8
|
+
increment = component.querySelector('[data-element="counter:increment"]')
|
|
9
|
+
value = component.querySelector('[data-element="counter:value"]')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('shows the initial value', () => {
|
|
13
|
+
expect(value).toContainText('0')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('increments the counter when the increment button is clicked', () => {
|
|
17
|
+
increment.click()
|
|
18
|
+
expect(value).toContainText('1')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('decrements the counter when the decrement button is clicked', () => {
|
|
22
|
+
decrement.click()
|
|
23
|
+
expect(value).toContainText('-1')
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
require "view_component/test_helpers"
|
|
3
|
+
|
|
4
|
+
class CounterComponentTest < ActionView::TestCase
|
|
5
|
+
include ViewComponent::TestHelpers
|
|
6
|
+
include LookbookHelper
|
|
7
|
+
|
|
8
|
+
def test_renders_counter_structure
|
|
9
|
+
render_inline(CounterComponent.new(initial_value: 5))
|
|
10
|
+
|
|
11
|
+
assert_selector ".counter"
|
|
12
|
+
assert_selector "button[data-element='counter:decrement']", text: "−"
|
|
13
|
+
assert_selector "span[data-element='counter:value']", text: "5"
|
|
14
|
+
assert_selector "button[data-element='counter:increment']", text: "+"
|
|
15
|
+
|
|
16
|
+
data = JSON.parse(page.find(".counter")["up-data"])
|
|
17
|
+
assert_equal 5, data["initialValue"]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_defaults_to_zero
|
|
21
|
+
render_inline(CounterComponent.new)
|
|
22
|
+
|
|
23
|
+
assert_selector "span[data-element='counter:value']", text: "0"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# https://viewcomponent.org/guide/previews.html#previews-as-test-cases
|
|
27
|
+
def test_render_view_component_preview
|
|
28
|
+
render_preview(:zero)
|
|
29
|
+
|
|
30
|
+
assert_selector "span[data-element='counter:value']", text: "0"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# See `app/helpers/lookbook_helper.rb`.
|
|
34
|
+
def test_render_lookbook_preview
|
|
35
|
+
render_preview_scenario("counter", "zero")
|
|
36
|
+
@page = Capybara.string(rendered)
|
|
37
|
+
|
|
38
|
+
assert_selector "span[data-element='counter:value']", text: "0"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Component Styleguide
|
|
2
|
+
|
|
3
|
+
- Use the provided `component` generator to scaffold new components. It also supports namespaces:
|
|
4
|
+
- `bin/rails g component Users::AvatarComponent`.
|
|
5
|
+
- Prefer a plural for your namespaces to avoid clashing with your models.
|
|
6
|
+
- Put your components into `app/components` and their tests into `test/components`.
|
|
7
|
+
- Keep filenames consistent.
|
|
8
|
+
- Delete files which you don't need for your component.
|
|
9
|
+
|
|
10
|
+
## ViewComponent
|
|
11
|
+
|
|
12
|
+
- Use `ApplicationComponent` as the base class.
|
|
13
|
+
- Prefer using slots over configuration via attributes.
|
|
14
|
+
- Consider using a Lookbook preview as the starting point for your view specs.
|
|
15
|
+
- Prefer private methods within components over helpers. Use concerns to share behavior.
|
|
16
|
+
|
|
17
|
+
## Unpoly Compilers
|
|
18
|
+
|
|
19
|
+
- Use `data-compiler` on the root element and `data-element` to connect your JS logic to the DOM.
|
|
20
|
+
- Pass data into components via `up-data`.
|
|
21
|
+
- Return a list of destructors from your compiler if necessary: [Unpoly Docs: destructor](https://unpoly.com/enhancing-elements#destructor)
|
|
22
|
+
- Consider using a Lookbook preview as the starting point for your Jasmine specs.
|
|
23
|
+
|
|
24
|
+
## CSS
|
|
25
|
+
|
|
26
|
+
- Use BEM-style CSS.
|
|
27
|
+
- Keep consistent with the naming conventions from the provided generator:
|
|
28
|
+
- We use an abbreviated style that differs from regular BEM.
|
|
29
|
+
- No underscores at all.
|
|
30
|
+
- Modifiers are just `-modifier` without repeating the block name.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
describe('ManualTestComponent', () => {
|
|
2
|
+
let heading, paragraph
|
|
3
|
+
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
const container = loadPreviewScenario('manual_test', 'with_multiple_top_level_tags')
|
|
6
|
+
heading = container.querySelector('h1')
|
|
7
|
+
paragraph = container.querySelector('p')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('loads both tags correctly', () => {
|
|
11
|
+
expect(heading).toContainText('Heading')
|
|
12
|
+
expect(paragraph).toContainText('Paragraph')
|
|
13
|
+
})
|
|
14
|
+
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { build, context, formatMessages } from 'esbuild'
|
|
2
|
+
import esbuildPluginImportGlob from 'esbuild-plugin-import-glob'
|
|
3
|
+
import esbuildPluginTextReplace from 'esbuild-plugin-text-replace'
|
|
4
|
+
import manifestPlugin from 'esbuild-manifest-plugin'
|
|
5
|
+
const { default: importGlob } = esbuildPluginImportGlob
|
|
6
|
+
|
|
7
|
+
const path = await import('path')
|
|
8
|
+
const fs = await import('fs')
|
|
9
|
+
|
|
10
|
+
const railsEnv = process.env.RAILS_ENV || 'development'
|
|
11
|
+
const outdir = path.join(process.cwd(), 'public/assets')
|
|
12
|
+
const errorFilePath = path.join(outdir, `esbuild_error_${railsEnv}.txt`)
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(outdir)) { fs.mkdirSync(outdir) }
|
|
15
|
+
|
|
16
|
+
async function handleErrors(errors) {
|
|
17
|
+
const formattedErrors = await formatMessages(errors, { kind: 'error' })
|
|
18
|
+
const output = formattedErrors.join('\n')
|
|
19
|
+
|
|
20
|
+
if (output) {
|
|
21
|
+
fs.writeFileSync(errorFilePath, output)
|
|
22
|
+
} else if (fs.existsSync(errorFilePath)) {
|
|
23
|
+
fs.truncate(errorFilePath, 0, () => {})
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const config = {
|
|
28
|
+
entryPoints: [
|
|
29
|
+
'application.js',
|
|
30
|
+
'jasmine.js',
|
|
31
|
+
'static.js'
|
|
32
|
+
],
|
|
33
|
+
bundle: true,
|
|
34
|
+
sourcemap: true,
|
|
35
|
+
format: 'esm',
|
|
36
|
+
outdir,
|
|
37
|
+
absWorkingDir: path.join(process.cwd(), 'app/assets'),
|
|
38
|
+
metafile: true,
|
|
39
|
+
publicPath: '/assets',
|
|
40
|
+
entryNames: '[dir]/[name]-[hash]',
|
|
41
|
+
assetNames: '[dir]/[name]-[hash]',
|
|
42
|
+
loader: {
|
|
43
|
+
'.css': 'css',
|
|
44
|
+
'.ico': 'copy',
|
|
45
|
+
'.jpg': 'copy',
|
|
46
|
+
'.png': 'copy',
|
|
47
|
+
'.svg': 'copy',
|
|
48
|
+
'.webp': 'copy',
|
|
49
|
+
'.json': 'copy',
|
|
50
|
+
'.ttf': 'file',
|
|
51
|
+
'.woff': 'file',
|
|
52
|
+
'.woff2': 'file',
|
|
53
|
+
},
|
|
54
|
+
plugins: [
|
|
55
|
+
esbuildPluginTextReplace({
|
|
56
|
+
include: /jasmine-core\/lib\/jasmine-core\/jasmine\.js/,
|
|
57
|
+
pattern: [
|
|
58
|
+
['let jasmineRequire;', 'let jasmineRequire; const global = window;'],
|
|
59
|
+
]
|
|
60
|
+
}),
|
|
61
|
+
importGlob(),
|
|
62
|
+
manifestPlugin(),
|
|
63
|
+
{
|
|
64
|
+
name: 'handleErrors',
|
|
65
|
+
setup: (build) => {
|
|
66
|
+
build.onEnd(async ({ errors }) => {
|
|
67
|
+
await handleErrors(errors)
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (process.argv.includes('--watch')) {
|
|
75
|
+
const ctx = await context(config)
|
|
76
|
+
ctx.watch()
|
|
77
|
+
} else {
|
|
78
|
+
await build(config)
|
|
79
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<div class="test-cases">
|
|
2
|
+
<% Lookbook.previews.each do |preview| %>
|
|
3
|
+
<% preview.send(:scenario_entities).each do |scenario| %>
|
|
4
|
+
<template data-component="<%= preview.lookup_path %>" data-scenario="<%= scenario.name %>">
|
|
5
|
+
<%= render_preview_scenario(preview, scenario) %>
|
|
6
|
+
</template>
|
|
7
|
+
<% end %>
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
This is a manual test scenario with multiple top-level tags to verify that the test framework can handle it.
|
|
12
|
+
Neither the ViewComponent::Preview nor Lookbook::Preview classes support multiple top-level tags, but if you
|
|
13
|
+
need such a test case, you can define it directly in the Jasmine layout as shown here.
|
|
14
|
+
-->
|
|
15
|
+
<template data-component="manual_test" data-scenario="with_multiple_top_level_tags">
|
|
16
|
+
<h1>Heading</h1>
|
|
17
|
+
<p>Paragraph</p>
|
|
18
|
+
</template>
|
|
19
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate, max-age=0">
|
|
5
|
+
<meta http-equiv="Pragma" content="no-cache">
|
|
6
|
+
<meta http-equiv="Expires" content="0">
|
|
7
|
+
<%= stylesheet_link_tag 'jasmine' %>
|
|
8
|
+
|
|
9
|
+
<!-- Referencing npm assets from HTML is currently broken. -->
|
|
10
|
+
<!-- <link href="<%#= asset_path('jasmine_favicon.png') %>" rel="icon" type="image/png"> -->
|
|
11
|
+
</head>
|
|
12
|
+
|
|
13
|
+
<body>
|
|
14
|
+
<%= yield %>
|
|
15
|
+
<%= javascript_include_tag 'jasmine' %>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class ComponentGenerator < Rails::Generators::NamedBase
|
|
2
|
+
source_root File.expand_path("../../templates/view_component/frozen_extensions", __dir__)
|
|
3
|
+
|
|
4
|
+
argument :attrs, type: :array, default: [], banner: "attribute"
|
|
5
|
+
|
|
6
|
+
def generate_component_with_preview_and_locale
|
|
7
|
+
in_root do
|
|
8
|
+
system("bin/rails generate view_component:component #{name} #{attrs.join(" ")} --preview --preview_path=test/components --locale --skip-test-framework --skip-template-engine")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add_erb_template
|
|
13
|
+
template "erb_template.html.erb.tt", File.join("app/components", class_path, "#{file_name}_component.html.erb")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add_css_file
|
|
17
|
+
template "bem_stylesheet.css.tt", File.join("app/components", class_path, "#{file_name}_component.css")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_unpoly_compiler
|
|
21
|
+
template "unpoly_compiler.js.tt", File.join("app/components", class_path, "#{file_name}_component.js")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add_view_spec
|
|
25
|
+
template "view_spec.rb.tt", File.join("test/components", class_path, "#{file_name}_component_test.rb")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def add_jasmine_spec
|
|
29
|
+
template "jasmine_spec.js.tt", File.join("test/components", class_path, "#{file_name}_component_spec.js")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def fix_module_nesting_via_rubocop_because_it_was_impossible_to_get_module_namespacing_to_work_as_expected
|
|
33
|
+
in_root do
|
|
34
|
+
system("bash", "-c", 'bin/rubocop --only Style/ClassAndModuleChildren,Layout/IndentationWidth,Layout/CommentIndentation --autocorrect-all --force-exclusion --config <(echo "Style/ClassAndModuleChildren: { EnforcedStyle: nested }")')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def js_attributes
|
|
41
|
+
attrs.map { |attr| attr.camelize(:lower) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def css_attributes
|
|
45
|
+
attrs.map(&:dasherize)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def bem_selectors
|
|
49
|
+
base = file_path.gsub("/", "---").dasherize
|
|
50
|
+
[
|
|
51
|
+
base,
|
|
52
|
+
*css_attributes.map { |attr| "#{base}--#{attr}" }
|
|
53
|
+
]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def up_data
|
|
57
|
+
data = js_attributes.zip(attrs).map { |js_attr, attr| "#{js_attr}: @#{attr}" }.join(", ")
|
|
58
|
+
"{ #{data} }"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def up_data_params
|
|
62
|
+
data = js_attributes.join(", ")
|
|
63
|
+
"{ #{data} }"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# https://github.com/ViewComponent/view_component/blob/main/lib/generators/view_component/preview/preview_generator.rb#L32
|
|
67
|
+
def render_signature
|
|
68
|
+
return if attributes.blank?
|
|
69
|
+
|
|
70
|
+
attributes.map { |attr| %(#{attr.name}: "#{attr.name}") }.join(", ")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
if Rake::Task.task_defined?("assets:precompile")
|
|
2
|
+
Rake::Task["assets:precompile"].clear
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
namespace :assets do
|
|
6
|
+
desc "Build frontend assets with yarn"
|
|
7
|
+
task :precompile do
|
|
8
|
+
system("yarn build", exception: true)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
if Rake::Task.task_defined?("test:prepare")
|
|
13
|
+
Rake::Task["test:prepare"].enhance([ "assets:precompile" ])
|
|
14
|
+
end
|
data/lib/generators/frozen/templates/ui/lib/templates/view_component/component/component.rb.tt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<% module_namespacing do -%>
|
|
2
|
+
class <%= class_name %><%= options[:skip_suffix] ? "" : "Component" %> < <%= parent_class %>
|
|
3
|
+
<%- if inline_template? -%>
|
|
4
|
+
<%= template_engine %>_template <<~<%= template_engine.upcase %>
|
|
5
|
+
<h1>Hello, World!</h1>
|
|
6
|
+
<%= template_engine.upcase %>
|
|
7
|
+
<%- end -%>
|
|
8
|
+
<%- if initialize_signature? -%>
|
|
9
|
+
def initialize(<%= initialize_signature %>)
|
|
10
|
+
<%= initialize_body %>
|
|
11
|
+
end
|
|
12
|
+
<%- end -%>
|
|
13
|
+
<%- if initialize_call_method_for_inline? -%>
|
|
14
|
+
def call
|
|
15
|
+
content_tag :h1, "Hello world!"<%= ", data: { controller: \"#{stimulus_controller}\" }" if options["stimulus"] %>
|
|
16
|
+
end
|
|
17
|
+
<%- end -%>
|
|
18
|
+
end
|
|
19
|
+
<% end -%>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<div class="<%= file_path.gsub('/', '---').dasherize %>" data-compiler="<%= file_path.gsub('/', '-') %>" up-data="<%%= <%= "{ #{attrs.map { |attr| "#{attr.camelize(:lower)}: @#{attr}" }.join(", ")} }" %>.to_json %>">
|
|
2
|
+
<% css_attributes.zip(js_attributes, attrs).each do |css_attr, js_attr, ruby_attr| -%>
|
|
3
|
+
<div class="<%= file_path.gsub('/', '---').dasherize %>--<%= css_attr %>" data-element="<%= file_path.gsub('/', '-') %>:<%= js_attr %>"><%%= @<%= ruby_attr %> %></div>
|
|
4
|
+
<% end -%>
|
|
5
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
describe('[data-compiler="<%= file_path.gsub('/', '-') %>"]', () => {
|
|
2
|
+
let <%= ["component", *js_attributes].join(', ') %>
|
|
3
|
+
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
const container = loadPreviewScenario('<%= file_path.gsub('/', '-') %>', 'default')
|
|
6
|
+
component = container.querySelector('.<%= file_path.gsub('/', '---').dasherize %>')
|
|
7
|
+
<% js_attributes.each do |attribute| -%>
|
|
8
|
+
<%= attribute %> = component.querySelector('[data-element="<%= file_path.gsub('/', '-') %>:<%= attribute %>"]')
|
|
9
|
+
<% end -%>
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('works', () => {
|
|
13
|
+
// TODO: implement spec
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
up.compiler('[data-compiler="<%= file_path.gsub('/', '-') %>"]', (element, <%= up_data_params %>) => {
|
|
2
|
+
<% js_attributes.each do |attribute| -%>
|
|
3
|
+
const <%= attribute %>Element = element.querySelector('[data-element="<%= file_path.gsub('/', '-') %>:<%= attribute %>"]')
|
|
4
|
+
<% end -%>
|
|
5
|
+
})
|