storybook_rails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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