storybook_rails 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +223 -0
- data/app/controllers/action_view/storybook/stories_controller.rb +44 -0
- data/app/views/action_view/storybook/stories/show.html.erb +1 -0
- data/lib/action_view/storybook.rb +33 -0
- data/lib/action_view/storybook/controls.rb +21 -0
- data/lib/action_view/storybook/controls/array_config.rb +36 -0
- data/lib/action_view/storybook/controls/boolean_config.rb +30 -0
- data/lib/action_view/storybook/controls/color_config.rb +27 -0
- data/lib/action_view/storybook/controls/control_config.rb +43 -0
- data/lib/action_view/storybook/controls/date_config.rb +34 -0
- data/lib/action_view/storybook/controls/number_config.rb +39 -0
- data/lib/action_view/storybook/controls/object_config.rb +21 -0
- data/lib/action_view/storybook/controls/options_config.rb +50 -0
- data/lib/action_view/storybook/controls/text_config.rb +13 -0
- data/lib/action_view/storybook/dsl.rb +14 -0
- data/lib/action_view/storybook/dsl/controls_dsl.rb +101 -0
- data/lib/action_view/storybook/dsl/story_dsl.rb +39 -0
- data/lib/action_view/storybook/engine.rb +46 -0
- data/lib/action_view/storybook/helpers.rb +13 -0
- data/lib/action_view/storybook/helpers/story_params.rb +5 -0
- data/lib/action_view/storybook/stories.rb +135 -0
- data/lib/action_view/storybook/story_config.rb +74 -0
- data/lib/action_view/storybook/tasks/write_stories_json.rake +13 -0
- data/lib/action_view/storybook/version.rb +7 -0
- metadata +225 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3156e720148b32541cf3a26364ab48422d8578c0f77ed0a5af2b79d6464c389c
|
4
|
+
data.tar.gz: 5c8aca3a712de9a7b937967629e465cac1319f339163f3a3e81fcd6877098ac6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1f4973da1272dfb432b3a6cbd8d9fc09430aad699887885f947550c95a6a5045e520c4cf200c2f34b876fb553e4e257d53a32efb5bb173dbaf3653078374d940
|
7
|
+
data.tar.gz: 277c3bf438cc3f5f438072abd7066a76d4b4242e3754d7b0d24cc48123afd037de985aff8a996b146bfb016b1af91b239c580cd6d5ac460d2269db3ddea6835c
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Jon Palmer
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
# storybook_rails
|
2
|
+
|
3
|
+
The `storybook_rails` gem provides a Ruby DSL for writing Storybook stories, allowing you to develop, preview, and test standard Rails view partials in [Storybook](https://github.com/storybookjs/storybook/).
|
4
|
+
|
5
|
+
## Prior Art
|
6
|
+
This gem is a fork of [ViewComponent::Storybook](https://github.com/jonspalmer/view_component_storybook) and has been adapted to work with standard Rails view partials.
|
7
|
+
|
8
|
+
## Features
|
9
|
+
* A Ruby DSL for writing Stories describing standard Rails view templates/partials
|
10
|
+
* A Rails controller backend for Storybook Server compatible with Storybook Controls Addon parameters
|
11
|
+
* More to come...
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
### Rails Installation
|
16
|
+
|
17
|
+
1. Add the `storybook_rails` gem, to your Gemfile: `gem 'storybook_rails'`
|
18
|
+
2. Run `bundle install`.
|
19
|
+
3. Add `require "action_view/storybook/engine"` to `config/application.rb`
|
20
|
+
4. Add `**/*.stories.json` to `.gitignore`
|
21
|
+
|
22
|
+
#### Configure Asset Hosts
|
23
|
+
|
24
|
+
If your views depend on Javascript, CSS or other assets served by the Rails application you will need to configure `asset_hosts`
|
25
|
+
apporpriately for your various environments. For local development this is a simple as adding to `config/development.rb`:
|
26
|
+
```ruby
|
27
|
+
Rails.application.configure do
|
28
|
+
...
|
29
|
+
config.action_controller.asset_host = 'http://localhost:3000'
|
30
|
+
...
|
31
|
+
end
|
32
|
+
```
|
33
|
+
Equivalent configuration will be necessary in `config/production.rb` or `application.rb` based you your deployment environments.
|
34
|
+
|
35
|
+
### Storybook Installation
|
36
|
+
|
37
|
+
1. Add Storybook server as a dev dependedncy. The Storybook Controls addon isn't needed but is strongly recommended
|
38
|
+
```sh
|
39
|
+
yarn add @storybook/server @storybook/addon-controls --dev
|
40
|
+
```
|
41
|
+
2. Add an NPM script to your package.json in order to start the storybook later in this guide
|
42
|
+
```json
|
43
|
+
{
|
44
|
+
"scripts": {
|
45
|
+
"storybook": "start-storybook"
|
46
|
+
}
|
47
|
+
}
|
48
|
+
```
|
49
|
+
3. Create the .storybook/main.js file to configure Storybook to find the json stories the gem creates. Also configure the Controls addon:
|
50
|
+
```javascript
|
51
|
+
module.exports = {
|
52
|
+
stories: ['../test/components/**/*.stories.json'],
|
53
|
+
addons: [
|
54
|
+
'@storybook/addon-controls',
|
55
|
+
],
|
56
|
+
};
|
57
|
+
```
|
58
|
+
4. Create the .storybook/preview.js file to configure Storybook with the Rails application url to call for the html content of the stories
|
59
|
+
```javascript
|
60
|
+
|
61
|
+
export const parameters = {
|
62
|
+
server: {
|
63
|
+
url: `http://localhost:3000/storybook`,
|
64
|
+
},
|
65
|
+
};
|
66
|
+
```
|
67
|
+
|
68
|
+
#### Webpacker
|
69
|
+
If your application uses Webpacker to compile your JavaScript and/or CSS, you will need to modify the default Storybook webpack configuration. Please see the [Storybook Webpack config](https://storybook.js.org/docs/react/configure/webpack) for more information on how to do that. Here's an example:
|
70
|
+
|
71
|
+
```javascript
|
72
|
+
// .storybook/main.js
|
73
|
+
|
74
|
+
const path = require('path');
|
75
|
+
const environment = require('../config/webpack/environment');
|
76
|
+
const { merge } = require('webpack-merge');
|
77
|
+
|
78
|
+
module.exports = {
|
79
|
+
webpackFinal: async (config, { configType }) => {
|
80
|
+
// `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
|
81
|
+
// You can change the configuration based on that.
|
82
|
+
// 'PRODUCTION' is used when building the static version of storybook.
|
83
|
+
let envConfig = environment.toWebpackConfig();
|
84
|
+
|
85
|
+
let entries = {
|
86
|
+
main: config.entry,
|
87
|
+
application: path.resolve(__dirname, '../app/javascript/packs/application.js')
|
88
|
+
}
|
89
|
+
|
90
|
+
config.entry = entries
|
91
|
+
|
92
|
+
// Storybook doesn't support .scss out of the box
|
93
|
+
config.module.rules.push({
|
94
|
+
test: /\.scss$/,
|
95
|
+
use: ['style-loader', 'css-loader', 'sass-loader'],
|
96
|
+
include: path.resolve(__dirname, '../'),
|
97
|
+
});
|
98
|
+
|
99
|
+
// merge Webpacker's config with Storybook's Webpack config
|
100
|
+
let merged = merge(config, {module: envConfig.module}, {plugins: envConfig.plugins}, {devtool: 'cheap-module-source-map'})
|
101
|
+
|
102
|
+
// Return the altered config
|
103
|
+
return config;
|
104
|
+
},
|
105
|
+
};
|
106
|
+
```
|
107
|
+
|
108
|
+
## Usage
|
109
|
+
|
110
|
+
### Writing Stories
|
111
|
+
|
112
|
+
Suppose our app has a shared `app/views/shared/_button.html.erb` partial:
|
113
|
+
|
114
|
+
```erb
|
115
|
+
<% variant_class_map = {
|
116
|
+
primary: "button",
|
117
|
+
secondary: "button-secondary",
|
118
|
+
outline: "button-outline",
|
119
|
+
} %>
|
120
|
+
|
121
|
+
<button class="<%= variant_class_map[variant.to_sym] %>">
|
122
|
+
<%= button_text %>
|
123
|
+
</button>
|
124
|
+
```
|
125
|
+
|
126
|
+
We can write a stories describing the `_button.html.erb` partial:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
# buttons/button_stories.rb
|
130
|
+
|
131
|
+
class Buttons::ButtonStories < ActionView::Storybook::Stories
|
132
|
+
self.title = "Buttons"
|
133
|
+
|
134
|
+
story(:primary) do
|
135
|
+
controls do
|
136
|
+
text(:button_text, "Primary")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
story(:secondary) do
|
141
|
+
controls do
|
142
|
+
text(:button_text, "Secondary")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
story(:outline) do
|
147
|
+
controls do
|
148
|
+
text(:button_text, "Outline")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
And a story template to render individual stories:
|
155
|
+
```erb
|
156
|
+
# buttons/button_stories.html.erb
|
157
|
+
|
158
|
+
<% story_name_class_map = {
|
159
|
+
primary: "button",
|
160
|
+
secondary: "button-secondary",
|
161
|
+
outline: "button-outline"
|
162
|
+
} %>
|
163
|
+
|
164
|
+
<%= render partial: 'shared/button',
|
165
|
+
locals: { variant: story_params[:story_name], button_text: story_params[:button_text] } %>
|
166
|
+
```
|
167
|
+
|
168
|
+
It's up to you how handle rendering your partials in Storybook, but `storybook_rails` will look for a view template that matches the story name (`buttons/button_stories.html.erb` in the example above. In addition, `storybook_rails` provides a `story_params` helper which provides quick access to the params and args specified in the story config. You can use these parameters in your view template to render each story dynamically. Or not. It's up to you.
|
169
|
+
|
170
|
+
### Generating Storybook Stories JSON
|
171
|
+
|
172
|
+
Generate the Storybook JSON stories by running the rake task:
|
173
|
+
```sh
|
174
|
+
rake storybook_rails:write_stories_json
|
175
|
+
```
|
176
|
+
|
177
|
+
### Start the Rails app and Storybook
|
178
|
+
|
179
|
+
In separate shells start the Rails app and Storybook
|
180
|
+
|
181
|
+
```sh
|
182
|
+
rails s
|
183
|
+
```
|
184
|
+
```sh
|
185
|
+
yarn start-storybook
|
186
|
+
```
|
187
|
+
|
188
|
+
Alternatively you can use tools like [Foreman](https://github.com/ddollar/foreman) to start both Rails and Storybook with one command.
|
189
|
+
|
190
|
+
### Configuration
|
191
|
+
|
192
|
+
By Default `storybook_rails` expects to find stories in the folder `test/components/stories`. This can be configured but setting `config.storybook_rails.stories_path` in `config/applicaion.rb`. For example, if you're using RSpec you might set the following configuration:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
config.storybook_rails.stories_path = Rails.root.join("spec/components/stories")
|
196
|
+
```
|
197
|
+
|
198
|
+
### The Story DSL
|
199
|
+
|
200
|
+
Coming Soon
|
201
|
+
|
202
|
+
#### Parameters
|
203
|
+
#### Layout
|
204
|
+
#### Controls
|
205
|
+
|
206
|
+
|
207
|
+
## Development
|
208
|
+
|
209
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
210
|
+
|
211
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
212
|
+
|
213
|
+
## Contributing
|
214
|
+
|
215
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/danieldpence/storybook_rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
216
|
+
|
217
|
+
## License
|
218
|
+
|
219
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
220
|
+
|
221
|
+
## Code of Conduct
|
222
|
+
|
223
|
+
Everyone interacting in the `storybook_rails` project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/danieldpence/storybook_rails/blob/master/CODE_OF_CONDUCT.md).
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/application_controller"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
module Storybook
|
7
|
+
class StoriesController < Rails::ApplicationController
|
8
|
+
prepend_view_path File.expand_path("../../../views", __dir__)
|
9
|
+
prepend_view_path Rails.root.join("app/views") if defined?(Rails.root)
|
10
|
+
prepend_view_path Rails.application.config.storybook_rails.stories_path
|
11
|
+
|
12
|
+
before_action :find_stories, :find_story, only: :show
|
13
|
+
before_action :require_local!, unless: :show_stories?
|
14
|
+
|
15
|
+
content_security_policy(false) if respond_to?(:content_security_policy)
|
16
|
+
|
17
|
+
def show
|
18
|
+
story_params = @story.values_from_params(params.permit!.to_h)
|
19
|
+
story_params.deep_merge!(story_name: params[:story_name])
|
20
|
+
|
21
|
+
render template: "#{@story.template}", layout: @story.layout, locals: { story_params: story_params }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def show_stories?
|
27
|
+
ActionView::Storybook.show_stories
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_stories
|
31
|
+
stories_name = params[:stories]
|
32
|
+
@stories = ActionView::Storybook::Stories.find_stories(stories_name)
|
33
|
+
|
34
|
+
head :not_found unless @stories
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_story
|
38
|
+
story_name = params[:story]
|
39
|
+
@story = @stories.find_story(story_name)
|
40
|
+
head :not_found unless @story
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<!-- Template rendered by host application -->
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model"
|
4
|
+
require "active_support/dependencies/autoload"
|
5
|
+
|
6
|
+
module ActionView
|
7
|
+
module Storybook
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
|
10
|
+
autoload :Controls
|
11
|
+
autoload :Stories
|
12
|
+
autoload :StoryConfig
|
13
|
+
autoload :Dsl
|
14
|
+
autoload :Helpers
|
15
|
+
|
16
|
+
include ActiveSupport::Configurable
|
17
|
+
# Set the location of component previews through app configuration:
|
18
|
+
#
|
19
|
+
# config.storybook_rails.stories_path = Rails.root.join("app/views/storybook/stories")
|
20
|
+
#
|
21
|
+
mattr_accessor :stories_path, instance_writer: false
|
22
|
+
|
23
|
+
# Enable or disable component previews through app configuration:
|
24
|
+
#
|
25
|
+
# config.storybook_rails.show_stories = false
|
26
|
+
#
|
27
|
+
# Defaults to +true+ for development environment
|
28
|
+
#
|
29
|
+
mattr_accessor :show_stories, instance_writer: false
|
30
|
+
|
31
|
+
ActiveSupport.run_load_hooks(:storybook_rails, self)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/dependencies/autoload"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
module Storybook
|
7
|
+
module Controls
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
|
10
|
+
autoload :ControlConfig
|
11
|
+
autoload :TextConfig
|
12
|
+
autoload :BooleanConfig
|
13
|
+
autoload :ColorConfig
|
14
|
+
autoload :NumberConfig
|
15
|
+
autoload :OptionsConfig
|
16
|
+
autoload :ArrayConfig
|
17
|
+
autoload :DateConfig
|
18
|
+
autoload :ObjectConfig
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
module Storybook
|
5
|
+
module Controls
|
6
|
+
class ArrayConfig < ControlConfig
|
7
|
+
attr_reader :separator
|
8
|
+
|
9
|
+
validates :separator, presence: true
|
10
|
+
|
11
|
+
def initialize(param, value, separator = ",", name: nil)
|
12
|
+
super(param, value, name: name)
|
13
|
+
@separator = separator
|
14
|
+
end
|
15
|
+
|
16
|
+
def type
|
17
|
+
:array
|
18
|
+
end
|
19
|
+
|
20
|
+
def value_from_param(param)
|
21
|
+
if param.is_a?(String)
|
22
|
+
param.split(separator)
|
23
|
+
else
|
24
|
+
super(param)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def csf_control_params
|
31
|
+
super.merge(separator: separator)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
module Storybook
|
5
|
+
module Controls
|
6
|
+
class BooleanConfig < ControlConfig
|
7
|
+
BOOLEAN_VALUES = [true, false].freeze
|
8
|
+
|
9
|
+
validates :value, inclusion: { in: BOOLEAN_VALUES }, unless: -> { value.nil? }
|
10
|
+
|
11
|
+
def type
|
12
|
+
:boolean
|
13
|
+
end
|
14
|
+
|
15
|
+
def value_from_param(param)
|
16
|
+
if param.is_a?(String) && param.present?
|
17
|
+
case param
|
18
|
+
when "true"
|
19
|
+
true
|
20
|
+
when "false"
|
21
|
+
false
|
22
|
+
end
|
23
|
+
else
|
24
|
+
super(param)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
module Storybook
|
5
|
+
module Controls
|
6
|
+
class ColorConfig < ControlConfig
|
7
|
+
attr_reader :preset_colors
|
8
|
+
|
9
|
+
def initialize(param, value, name: nil, preset_colors: nil)
|
10
|
+
super(param, value, name: name)
|
11
|
+
@preset_colors = preset_colors
|
12
|
+
end
|
13
|
+
|
14
|
+
def type
|
15
|
+
:color
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def csf_control_params
|
21
|
+
params = super
|
22
|
+
params.merge(presetColors: preset_colors).compact
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|