├── .coveralls.yml
├── .formatter.exs
├── .gitignore
├── .tool-versions
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── config
└── config.exs
├── example
├── .formatter.exs
├── .gitignore
├── README.md
├── assets
│ ├── .babelrc
│ ├── css
│ │ └── app.scss
│ ├── js
│ │ ├── app.js
│ │ └── socket.js
│ ├── package-lock.json
│ ├── package.json
│ ├── static
│ │ ├── favicon.ico
│ │ ├── images
│ │ │ └── phoenix.png
│ │ └── robots.txt
│ └── webpack.config.js
├── config
│ ├── config.exs
│ ├── dev.exs
│ ├── prod.exs
│ └── test.exs
├── lib
│ ├── example.ex
│ ├── example
│ │ └── application.ex
│ ├── example_web.ex
│ └── example_web
│ │ ├── channels
│ │ └── user_socket.ex
│ │ ├── controllers
│ │ └── page_controller.ex
│ │ ├── endpoint.ex
│ │ ├── gettext.ex
│ │ ├── router.ex
│ │ ├── templates
│ │ ├── layout
│ │ │ └── app.html.eex
│ │ └── page
│ │ │ └── index.html.eex
│ │ └── views
│ │ ├── error_helpers.ex
│ │ ├── error_view.ex
│ │ ├── layout_view.ex
│ │ └── page_view.ex
├── mix.exs
├── mix.lock
├── priv
│ └── gettext
│ │ ├── en
│ │ └── LC_MESSAGES
│ │ │ └── errors.po
│ │ └── errors.pot
└── test
│ ├── example_web
│ ├── controllers
│ │ └── page_controller_test.exs
│ └── views
│ │ ├── error_view_test.exs
│ │ ├── layout_view_test.exs
│ │ └── page_view_test.exs
│ ├── support
│ ├── channel_case.ex
│ └── conn_case.ex
│ └── test_helper.exs
├── lib
├── harmonium.ex
└── harmonium
│ └── table.ex
├── mix.exs
├── mix.lock
└── test
├── harmonium
└── table_test.exs
├── harmonium_test.exs
└── test_helper.exs
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | multi:
2 | excoveralls: cover/excoveralls.json
3 |
--------------------------------------------------------------------------------
/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | harmonium-*.tar
24 |
25 | # vscode stuff
26 | .elixir_ls
27 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | erlang 24.0.5
2 | elixir 1.12.2-otp-24
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: elixir
3 | elixir:
4 | - 1.12
5 | otp_release:
6 | - 24.0
7 | cache:
8 | directories:
9 | - _build
10 | - deps
11 | env:
12 | global:
13 | - HEX_USERNAME=jwietelmann
14 | install:
15 | - gem install coveralls-multi --no-document
16 | - mix local.hex --force
17 | - mix local.rebar --force
18 | - mix deps.get
19 | script:
20 | - MIX_ENV=test mix coveralls.json
21 | - coveralls-multi
22 | before_deploy:
23 | - mix compile
24 | deploy:
25 | skip_cleanup: true
26 | # https://docs.travis-ci.com/user/deployment/script/
27 | # > `script` must be a scalar pointing to an executable file or command.
28 | provider: script
29 | # http://yaml.org/spec/1.2/spec.html#id2779048
30 | # `>-` indicates the line folding.
31 | script: mix hex.publish --yes
32 | on:
33 | tags: true
34 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [0.1.0] - 2018-06-13
11 |
12 | ### Added
13 |
14 | * First published release
15 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at support@revelry.co. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing and Development
2 |
3 | ## Development Setup
4 |
5 | * Clone repository
6 | * `mix deps.get` to get dependencies
7 | * `mix compile` to compile project
8 | * `mix test` to run tests
9 |
10 | ## Submitting Changes
11 |
12 | 1. Fork the project
13 | 2. Create a new topic branch to contain your feature, change, or fix.
14 | 3. Make sure all the tests are still passing.
15 | 4. Implement your feature, change, or fix. Make sure to write tests, update and/or add documentation.
16 | 5. Push your topic branch up to your fork.
17 | 6. Open a Pull Request with a clear title and description.
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2018 Revelry Labs LLC
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
5 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
6 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7 |
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions
9 | of the Software.
10 |
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
12 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
13 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
14 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
15 | DEALINGS IN THE SOFTWARE.
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Harmonium
2 |
3 | [](https://travis-ci.org/revelrylabs/phoenix_harmonium)
4 | [](https://hex.pm/packages/harmonium)
5 | [](https://opensource.org/licenses/MIT)
6 | [](https://opencov.prod.revelry.net/projects/7)
7 |
8 | Phoenix Framework view helpers for Harmonium-styled HTML without React.
9 |
10 | Harmonium was originally conceived as a set of styled React components ([harmonium.revelry.co](https://harmonium.revelry.co/)). This library brings the style and markup framework of Harmonium into Phoenix, but leaves the JavaScript behind.
11 |
12 | Here are a just few of the features:
13 |
14 | - Flexible grid layout system
15 | - Enhanced form helpers that decorate your inputs with errors and help text in a uniform way
16 | - Functions for inserting just the right CSS class names, for those moments when you want to go off the pre-built path
17 |
18 | ## Installation
19 |
20 | Add `harmonium` to your list of dependencies in `mix.exs` before running `mix deps.get`:
21 |
22 | ```elixir
23 | def deps do
24 | [
25 | {:harmonium, "~> 2.1.1"}
26 | ]
27 | end
28 | ```
29 |
30 | Then, in `config.exs`, pass in your Phoenix application's error translator function:
31 |
32 | ```
33 | config :harmonium,
34 | error_helper: {YourAppWeb.ErrorHelpers, :translate_error}
35 | ```
36 |
37 | From your app's root directory, run this command to get the `harmonium` package from NPM, which contains the SCSS you'll need:
38 |
39 | ```bash
40 | $(cd assets && npm install --save harmonium)
41 | ```
42 |
43 | In `assets/app.scss`, import the SCSS:
44 |
45 | ```scss
46 | @import '~harmonium/scss/app';
47 | ```
48 |
49 | For more details, and a set of Starter Settings for configuring Harmonium styles, go to [harmonium.revelry.co](https://harmonium.revelry.co/)
50 |
51 | ## Example Usage
52 |
53 | Checkout the [documentation](https://hexdocs.pm/harmonium) for more examples and a full list of functions.
54 |
55 | ```elixir
56 | <%= form_for @changeset, @action, fn f -> %>
57 | <%= row do %>
58 | <%= col medium: 6, large: 4 do %>
59 | <%= text_input_stack f, :username, label: "Username", help: "Pick a good one. You can't change it later." %>
60 | <% end %>
61 | <%= col medium: 6, large: 4 do %>
62 | <%= password_input_stack f, :password, label: "Password", help: "Make it strong!" %>
63 | <% end %>
64 | <%= col medium: 6, large: 4 do %>
65 | <%= password_input_stack f, :password, label: "Password (confirm)", help: "Type it again." %>
66 | <% end %>
67 | <% end %>
68 | <%= row do %>
69 | <%= col do %>
70 | <%= single_checkbox f, :subscribe_to_newsletter, label: "Please, please, please subscribe to my newsletter." %>
71 | <% end %>
72 | <% end %>
73 | <%= row do %>
74 | <%= col do %>
75 | <%= submit "Save", class: button_class(expanded: true) %>
76 | <% end %>
77 | <% end %>
78 | <% end %>
79 | ```
80 |
81 | ## Making a new release
82 |
83 | To deploy and update to the harmonium hex package, you first need to increment version number in `mix.exs`. Afterwards all that needs to be done is to create a new release tag for the new version number, and Travis should deploy the package automatically.
84 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | use Mix.Config
4 |
5 | # This configuration is loaded before any dependency and is restricted
6 | # to this project. If another project depends on this project, this
7 | # file won't be loaded nor affect the parent project. For this reason,
8 | # if you want to provide default values for your application for
9 | # 3rd-party users, it should be done in your "mix.exs" file.
10 |
11 | # You can configure your application as:
12 | #
13 | # config :harmonium, key: :value
14 | #
15 | # and access this configuration in your application as:
16 | #
17 | # Application.get_env(:harmonium, :key)
18 | #
19 | # You can also configure a 3rd-party app:
20 | #
21 | # config :logger, level: :info
22 | #
23 |
24 | # It is also possible to import configuration files, relative to this
25 | # directory. For example, you can emulate configuration per environment
26 | # by uncommenting the line below and defining dev.exs, test.exs and such.
27 | # Configuration from the imported file will override the ones defined
28 | # here (which is why it is important to import them last).
29 | #
30 | # import_config "#{Mix.env}.exs"
31 |
--------------------------------------------------------------------------------
/example/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | import_deps: [:phoenix],
3 | inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | example-*.tar
24 |
25 | # If NPM crashes, it generates a log, let's ignore it too.
26 | npm-debug.log
27 |
28 | # The directory NPM downloads your dependencies sources to.
29 | /assets/node_modules/
30 |
31 | # Since we are building assets from assets/,
32 | # we ignore priv/static. You may want to comment
33 | # this depending on your deployment strategy.
34 | /priv/static/
35 |
36 | # Files matching config/*.secret.exs pattern contain sensitive
37 | # data and you should not commit them into version control.
38 | #
39 | # Alternatively, you may comment the line below and commit the
40 | # secrets files as long as you replace their contents by environment
41 | # variables.
42 | /config/*.secret.exs
43 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Example
2 |
3 | To start your Phoenix server:
4 |
5 | * Install dependencies with `mix deps.get`
6 | * Install Node.js dependencies with `cd assets && npm install`
7 | * Start Phoenix endpoint with `mix phx.server`
8 |
9 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
10 |
11 | Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
12 |
13 | ## Learn more
14 |
15 | * Official website: http://www.phoenixframework.org/
16 | * Guides: https://hexdocs.pm/phoenix/overview.html
17 | * Docs: https://hexdocs.pm/phoenix
18 | * Mailing list: http://groups.google.com/group/phoenix-talk
19 | * Source: https://github.com/phoenixframework/phoenix
20 |
--------------------------------------------------------------------------------
/example/assets/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/example/assets/css/app.scss:
--------------------------------------------------------------------------------
1 | @import '~harmonium/scss/app';
2 |
--------------------------------------------------------------------------------
/example/assets/js/app.js:
--------------------------------------------------------------------------------
1 | // We need to import the CSS so that webpack will load it.
2 | // The MiniCssExtractPlugin is used to separate it out into
3 | // its own CSS file.
4 | import "../css/app.scss"
5 |
6 | // webpack automatically bundles all modules in your
7 | // entry points. Those entry points can be configured
8 | // in "webpack.config.js".
9 | //
10 | // Import dependencies
11 | //
12 | //import "phoenix_html"
13 |
14 | // Import local files
15 | //
16 | // Local files can be imported directly using relative paths, for example:
17 | // import socket from "./socket"
18 |
--------------------------------------------------------------------------------
/example/assets/js/socket.js:
--------------------------------------------------------------------------------
1 | // NOTE: The contents of this file will only be executed if
2 | // you uncomment its entry in "assets/js/app.js".
3 |
4 | // To use Phoenix channels, the first step is to import Socket,
5 | // and connect at the socket path in "lib/web/endpoint.ex".
6 | //
7 | // Pass the token on params as below. Or remove it
8 | // from the params if you are not using authentication.
9 | import {Socket} from "phoenix"
10 |
11 | let socket = new Socket("/socket", {params: {token: window.userToken}})
12 |
13 | // When you connect, you'll often need to authenticate the client.
14 | // For example, imagine you have an authentication plug, `MyAuth`,
15 | // which authenticates the session and assigns a `:current_user`.
16 | // If the current user exists you can assign the user's token in
17 | // the connection for use in the layout.
18 | //
19 | // In your "lib/web/router.ex":
20 | //
21 | // pipeline :browser do
22 | // ...
23 | // plug MyAuth
24 | // plug :put_user_token
25 | // end
26 | //
27 | // defp put_user_token(conn, _) do
28 | // if current_user = conn.assigns[:current_user] do
29 | // token = Phoenix.Token.sign(conn, "user socket", current_user.id)
30 | // assign(conn, :user_token, token)
31 | // else
32 | // conn
33 | // end
34 | // end
35 | //
36 | // Now you need to pass this token to JavaScript. You can do so
37 | // inside a script tag in "lib/web/templates/layout/app.html.eex":
38 | //
39 | //
40 | //
41 | // You will need to verify the user token in the "connect/3" function
42 | // in "lib/web/channels/user_socket.ex":
43 | //
44 | // def connect(%{"token" => token}, socket, _connect_info) do
45 | // # max_age: 1209600 is equivalent to two weeks in seconds
46 | // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
47 | // {:ok, user_id} ->
48 | // {:ok, assign(socket, :user, user_id)}
49 | // {:error, reason} ->
50 | // :error
51 | // end
52 | // end
53 | //
54 | // Finally, connect to the socket:
55 | socket.connect()
56 |
57 | // Now that you are connected, you can join channels with a topic:
58 | let channel = socket.channel("topic:subtopic", {})
59 | channel.join()
60 | .receive("ok", resp => { console.log("Joined successfully", resp) })
61 | .receive("error", resp => { console.log("Unable to join", resp) })
62 |
63 | export default socket
64 |
--------------------------------------------------------------------------------
/example/assets/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {},
3 | "license": "MIT",
4 | "scripts": {
5 | "deploy": "webpack --mode production",
6 | "watch": "webpack --mode development --watch"
7 | },
8 | "dependencies": {
9 | "harmonium": "^4.3.2",
10 | "phoenix": "file:../deps/phoenix",
11 | "phoenix_html": "file:../deps/phoenix_html"
12 | },
13 | "devDependencies": {
14 | "@babel/core": "^7.0.0",
15 | "@babel/preset-env": "^7.0.0",
16 | "babel-loader": "^8.0.0",
17 | "copy-webpack-plugin": "^4.5.0",
18 | "css-loader": "^0.28.10",
19 | "js-yaml": ">=3.13.1",
20 | "mem": ">=4.0.0",
21 | "mini-css-extract-plugin": "^0.4.0",
22 | "node-sass": "^4.10.0",
23 | "optimize-css-assets-webpack-plugin": "^4.0.0",
24 | "sass-loader": "^7.1.0",
25 | "uglifyjs-webpack-plugin": "^1.2.4",
26 | "webpack": "4.4.0",
27 | "webpack-cli": "^2.0.10"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/example/assets/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/revelrylabs/phoenix_harmonium/f473295d93c9dcb5886cc71f6b1b2e3d097a2335/example/assets/static/favicon.ico
--------------------------------------------------------------------------------
/example/assets/static/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/revelrylabs/phoenix_harmonium/f473295d93c9dcb5886cc71f6b1b2e3d097a2335/example/assets/static/images/phoenix.png
--------------------------------------------------------------------------------
/example/assets/static/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/example/assets/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const glob = require('glob');
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
5 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
6 | const CopyWebpackPlugin = require('copy-webpack-plugin');
7 |
8 | module.exports = (env, options) => ({
9 | optimization: {
10 | minimizer: [
11 | new UglifyJsPlugin({ cache: true, parallel: true, sourceMap: false }),
12 | new OptimizeCSSAssetsPlugin({})
13 | ]
14 | },
15 | entry: {
16 | './js/app.js': ['./js/app.js'].concat(glob.sync('./vendor/**/*.js'))
17 | },
18 | output: {
19 | filename: 'app.js',
20 | path: path.resolve(__dirname, '../priv/static/js')
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.js$/,
26 | exclude: /node_modules/,
27 | use: {
28 | loader: 'babel-loader'
29 | }
30 | },
31 | {
32 | test: /\.(css|scss|sass)$/,
33 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
34 | }
35 | ]
36 | },
37 | plugins: [
38 | new MiniCssExtractPlugin({ filename: '../css/app.css' }),
39 | new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
40 | ]
41 | });
42 |
--------------------------------------------------------------------------------
/example/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 |
7 | # General application configuration
8 | use Mix.Config
9 |
10 | # Configures the endpoint
11 | config :example, ExampleWeb.Endpoint,
12 | url: [host: "localhost"],
13 | secret_key_base: "APXyEcfgq9exE+xPaSjh8pxBfzHTTxLMfaaN5hHR23e7EEwG+xYXRyBcFkFWrNg1",
14 | render_errors: [view: ExampleWeb.ErrorView, accepts: ~w(html json)],
15 | pubsub: [name: Example.PubSub, adapter: Phoenix.PubSub.PG2]
16 |
17 | # Configures Elixir's Logger
18 | config :logger, :console,
19 | format: "$time $metadata[$level] $message\n",
20 | metadata: [:request_id]
21 |
22 | # Use Jason for JSON parsing in Phoenix
23 | config :phoenix, :json_library, Jason
24 |
25 | config :harmonium,
26 | error_helper: {ExampleWeb.ErrorHelpers, :translate_error}
27 |
28 | # Import environment specific config. This must remain at the bottom
29 | # of this file so it overrides the configuration defined above.
30 | import_config "#{Mix.env()}.exs"
31 |
--------------------------------------------------------------------------------
/example/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with webpack to recompile .js and .css sources.
9 | config :example, ExampleWeb.Endpoint,
10 | http: [port: 4000],
11 | debug_errors: true,
12 | code_reloader: true,
13 | check_origin: false,
14 | watchers: [
15 | node: [
16 | "node_modules/webpack/bin/webpack.js",
17 | "--mode",
18 | "development",
19 | "--watch-stdin",
20 | cd: Path.expand("../assets", __DIR__)
21 | ]
22 | ]
23 |
24 | # ## SSL Support
25 | #
26 | # In order to use HTTPS in development, a self-signed
27 | # certificate can be generated by running the following
28 | # Mix task:
29 | #
30 | # mix phx.gen.cert
31 | #
32 | # Note that this task requires Erlang/OTP 20 or later.
33 | # Run `mix help phx.gen.cert` for more information.
34 | #
35 | # The `http:` config above can be replaced with:
36 | #
37 | # https: [
38 | # port: 4001,
39 | # cipher_suite: :strong,
40 | # keyfile: "priv/cert/selfsigned_key.pem",
41 | # certfile: "priv/cert/selfsigned.pem"
42 | # ],
43 | #
44 | # If desired, both `http:` and `https:` keys can be
45 | # configured to run both http and https servers on
46 | # different ports.
47 |
48 | # Watch static and templates for browser reloading.
49 | config :example, ExampleWeb.Endpoint,
50 | live_reload: [
51 | patterns: [
52 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
53 | ~r{priv/gettext/.*(po)$},
54 | ~r{lib/example_web/views/.*(ex)$},
55 | ~r{lib/example_web/templates/.*(eex)$}
56 | ]
57 | ]
58 |
59 | # Do not include metadata nor timestamps in development logs
60 | config :logger, :console, format: "[$level] $message\n"
61 |
62 | # Set a higher stacktrace during development. Avoid configuring such
63 | # in production as building large stacktraces may be expensive.
64 | config :phoenix, :stacktrace_depth, 20
65 |
66 | # Initialize plugs at runtime for faster development compilation
67 | config :phoenix, :plug_init_mode, :runtime
68 |
--------------------------------------------------------------------------------
/example/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, don't forget to configure the url host
4 | # to something meaningful, Phoenix uses this information
5 | # when generating URLs.
6 | #
7 | # Note we also include the path to a cache manifest
8 | # containing the digested version of static files. This
9 | # manifest is generated by the `mix phx.digest` task,
10 | # which you should run after static files are built and
11 | # before starting your production server.
12 | config :example, ExampleWeb.Endpoint,
13 | http: [:inet6, port: System.get_env("PORT") || 4000],
14 | url: [host: "example.com", port: 80],
15 | cache_static_manifest: "priv/static/cache_manifest.json"
16 |
17 | # Do not print debug messages in production
18 | config :logger, level: :info
19 |
20 | # ## SSL Support
21 | #
22 | # To get SSL working, you will need to add the `https` key
23 | # to the previous section and set your `:url` port to 443:
24 | #
25 | # config :example, ExampleWeb.Endpoint,
26 | # ...
27 | # url: [host: "example.com", port: 443],
28 | # https: [
29 | # :inet6,
30 | # port: 443,
31 | # cipher_suite: :strong,
32 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
33 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
34 | # ]
35 | #
36 | # The `cipher_suite` is set to `:strong` to support only the
37 | # latest and more secure SSL ciphers. This means old browsers
38 | # and clients may not be supported. You can set it to
39 | # `:compatible` for wider support.
40 | #
41 | # `:keyfile` and `:certfile` expect an absolute path to the key
42 | # and cert in disk or a relative path inside priv, for example
43 | # "priv/ssl/server.key". For all supported SSL configuration
44 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
45 | #
46 | # We also recommend setting `force_ssl` in your endpoint, ensuring
47 | # no data is ever sent via http, always redirecting to https:
48 | #
49 | # config :example, ExampleWeb.Endpoint,
50 | # force_ssl: [hsts: true]
51 | #
52 | # Check `Plug.SSL` for all available options in `force_ssl`.
53 |
54 | # ## Using releases (distillery)
55 | #
56 | # If you are doing OTP releases, you need to instruct Phoenix
57 | # to start the server for all endpoints:
58 | #
59 | # config :phoenix, :serve_endpoints, true
60 | #
61 | # Alternatively, you can configure exactly which server to
62 | # start per endpoint:
63 | #
64 | # config :example, ExampleWeb.Endpoint, server: true
65 | #
66 | # Note you can't rely on `System.get_env/1` when using releases.
67 | # See the releases documentation accordingly.
68 |
69 | # Finally import the config/prod.secret.exs which should be versioned
70 | # separately.
71 | import_config "prod.secret.exs"
72 |
--------------------------------------------------------------------------------
/example/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :example, ExampleWeb.Endpoint,
6 | http: [port: 4002],
7 | server: false
8 |
9 | # Print only warnings and errors during test
10 | config :logger, level: :warn
11 |
--------------------------------------------------------------------------------
/example/lib/example.ex:
--------------------------------------------------------------------------------
1 | defmodule Example do
2 | @moduledoc """
3 | Example keeps the contexts that define your domain
4 | and business logic.
5 |
6 | Contexts are also responsible for managing your data, regardless
7 | if it comes from the database, an external API or others.
8 | """
9 | end
10 |
--------------------------------------------------------------------------------
/example/lib/example/application.ex:
--------------------------------------------------------------------------------
1 | defmodule Example.Application do
2 | # See https://hexdocs.pm/elixir/Application.html
3 | # for more information on OTP Applications
4 | @moduledoc false
5 |
6 | use Application
7 |
8 | def start(_type, _args) do
9 | # List all child processes to be supervised
10 | children = [
11 | # Start the endpoint when the application starts
12 | ExampleWeb.Endpoint
13 | # Starts a worker by calling: Example.Worker.start_link(arg)
14 | # {Example.Worker, arg},
15 | ]
16 |
17 | # See https://hexdocs.pm/elixir/Supervisor.html
18 | # for other strategies and supported options
19 | opts = [strategy: :one_for_one, name: Example.Supervisor]
20 | Supervisor.start_link(children, opts)
21 | end
22 |
23 | # Tell Phoenix to update the endpoint configuration
24 | # whenever the application is updated.
25 | def config_change(changed, _new, removed) do
26 | ExampleWeb.Endpoint.config_change(changed, removed)
27 | :ok
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/example/lib/example_web.ex:
--------------------------------------------------------------------------------
1 | defmodule ExampleWeb do
2 | @moduledoc """
3 | The entrypoint for defining your web interface, such
4 | as controllers, views, channels and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use ExampleWeb, :controller
9 | use ExampleWeb, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below. Instead, define any helper function in modules
17 | and import those modules here.
18 | """
19 |
20 | def controller do
21 | quote do
22 | use Phoenix.Controller, namespace: ExampleWeb
23 |
24 | import Plug.Conn
25 | import ExampleWeb.Gettext
26 | alias ExampleWeb.Router.Helpers, as: Routes
27 | end
28 | end
29 |
30 | def view do
31 | quote do
32 | use Phoenix.View,
33 | root: "lib/example_web/templates",
34 | namespace: ExampleWeb
35 |
36 | # Import convenience functions from controllers
37 | import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]
38 |
39 | # Use all HTML functionality (forms, tags, etc)
40 | use Phoenix.HTML
41 | import Harmonium
42 |
43 | import ExampleWeb.ErrorHelpers
44 | import ExampleWeb.Gettext
45 | alias ExampleWeb.Router.Helpers, as: Routes
46 | end
47 | end
48 |
49 | def router do
50 | quote do
51 | use Phoenix.Router
52 | import Plug.Conn
53 | import Phoenix.Controller
54 | end
55 | end
56 |
57 | def channel do
58 | quote do
59 | use Phoenix.Channel
60 | import ExampleWeb.Gettext
61 | end
62 | end
63 |
64 | @doc """
65 | When used, dispatch to the appropriate controller/view/etc.
66 | """
67 | defmacro __using__(which) when is_atom(which) do
68 | apply(__MODULE__, which, [])
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/example/lib/example_web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule ExampleWeb.UserSocket do
2 | use Phoenix.Socket
3 |
4 | ## Channels
5 | # channel "room:*", ExampleWeb.RoomChannel
6 |
7 | # Socket params are passed from the client and can
8 | # be used to verify and authenticate a user. After
9 | # verification, you can put default assigns into
10 | # the socket that will be set for all channels, ie
11 | #
12 | # {:ok, assign(socket, :user_id, verified_user_id)}
13 | #
14 | # To deny connection, return `:error`.
15 | #
16 | # See `Phoenix.Token` documentation for examples in
17 | # performing token verification on connect.
18 | def connect(_params, socket, _connect_info) do
19 | {:ok, socket}
20 | end
21 |
22 | # Socket id's are topics that allow you to identify all sockets for a given user:
23 | #
24 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}"
25 | #
26 | # Would allow you to broadcast a "disconnect" event and terminate
27 | # all active sockets and channels for a given user:
28 | #
29 | # ExampleWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
30 | #
31 | # Returning `nil` makes this socket anonymous.
32 | def id(_socket), do: nil
33 | end
34 |
--------------------------------------------------------------------------------
/example/lib/example_web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule ExampleWeb.PageController do
2 | use ExampleWeb, :controller
3 |
4 | def index(conn, _params) do
5 | render(conn, "index.html")
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/example/lib/example_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule ExampleWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :example
3 |
4 | socket "/socket", ExampleWeb.UserSocket,
5 | websocket: true,
6 | longpoll: false
7 |
8 | # Serve at "/" the static files from "priv/static" directory.
9 | #
10 | # You should set gzip to true if you are running phx.digest
11 | # when deploying your static files in production.
12 | plug Plug.Static,
13 | at: "/",
14 | from: :example,
15 | gzip: false,
16 | only: ~w(css fonts images js favicon.ico robots.txt)
17 |
18 | # Code reloading can be explicitly enabled under the
19 | # :code_reloader configuration of your endpoint.
20 | if code_reloading? do
21 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
22 | plug Phoenix.LiveReloader
23 | plug Phoenix.CodeReloader
24 | end
25 |
26 | plug Plug.RequestId
27 | plug Plug.Logger
28 |
29 | plug Plug.Parsers,
30 | parsers: [:urlencoded, :multipart, :json],
31 | pass: ["*/*"],
32 | json_decoder: Phoenix.json_library()
33 |
34 | plug Plug.MethodOverride
35 | plug Plug.Head
36 |
37 | # The session will be stored in the cookie and signed,
38 | # this means its contents can be read but not tampered with.
39 | # Set :encryption_salt if you would also like to encrypt it.
40 | plug Plug.Session,
41 | store: :cookie,
42 | key: "_example_key",
43 | signing_salt: "1H/oIyPc"
44 |
45 | plug ExampleWeb.Router
46 | end
47 |
--------------------------------------------------------------------------------
/example/lib/example_web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule ExampleWeb.Gettext do
2 | @moduledoc """
3 | A module providing Internationalization with a gettext-based API.
4 |
5 | By using [Gettext](https://hexdocs.pm/gettext),
6 | your module gains a set of macros for translations, for example:
7 |
8 | import ExampleWeb.Gettext
9 |
10 | # Simple translation
11 | gettext("Here is the string to translate")
12 |
13 | # Plural translation
14 | ngettext("Here is the string to translate",
15 | "Here are the strings to translate",
16 | 3)
17 |
18 | # Domain-based translation
19 | dgettext("errors", "Here is the error message to translate")
20 |
21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
22 | """
23 | use Gettext, otp_app: :example
24 | end
25 |
--------------------------------------------------------------------------------
/example/lib/example_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule ExampleWeb.Router do
2 | use ExampleWeb, :router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_flash
8 | plug :protect_from_forgery
9 | plug :put_secure_browser_headers
10 | end
11 |
12 | pipeline :api do
13 | plug :accepts, ["json"]
14 | end
15 |
16 | scope "/", ExampleWeb do
17 | pipe_through :browser
18 |
19 | get "/", PageController, :index
20 | end
21 |
22 | # Other scopes may use custom stacks.
23 | # scope "/api", ExampleWeb do
24 | # pipe_through :api
25 | # end
26 | end
27 |
--------------------------------------------------------------------------------
/example/lib/example_web/templates/layout/app.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Example · Phoenix Framework
8 | "/>
9 |
10 |
11 | <%= render @view_module, @view_template, assigns %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/lib/example_web/templates/page/index.html.eex:
--------------------------------------------------------------------------------
1 | <%
2 |
3 | # Some useful data vars that we use in the examples below
4 |
5 | airports = [{"Louis Armstrong", "MSY"}, {"John F. Kennedy", "JFK"}]
6 |
7 | %>
8 |
9 | <%# Styles that are only useful for the styleguide %>
10 |
16 |
17 | <%= row do %>
18 |
19 | <%= col do %>
20 |
21 | <%= row do %>
22 | <%= col do %>
23 |
Styleguide
24 | <% end %>
25 | <% end %>
26 |
27 |
28 | <%= row do %>
29 | <%= col do %>
30 |
Cards
31 | <% end %>
32 |
33 | <%= col do %>
34 | <%= card do %>
35 | <%= card_header do %>
36 | <%= row do %>
37 | <%= col do %>
Header text
<% end %>
38 | <% end %>
39 | <% end %>
40 | <%= card_body do %>
41 | <%= row do %>
42 | <%= col do %>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
<% end %>
43 | <% end %>
44 | <% end %>
45 | <%= card_footer do %>
46 | <%= row do %>
47 | <%= col do %>Footer text<% end %>
48 | <% end %>
49 | <% end %>
50 | <% end %>
51 | <% end %>
52 | <% end %>
53 |
54 |
55 |
56 |
57 | <%= row do %>
58 | <%= col do %>
59 |
87 |
88 | <%= button_group do %>
89 | <%= link "Button 1", to: "#", class: button_class(secondary: true) %>
90 | <%= link "Button 2", to: "#", class: button_class() %>
91 | <%= link "Button 3", to: "#", class: button_class(disabled: true) %>
92 | <% end %>
93 | <% end %>
94 | <% end %>
95 |
96 |
97 |
98 |
99 | <%= mock_form fn f -> %>
100 |
101 | <%= row do %>
102 | <%= col do %>
103 |
Form Elements
104 |
105 | Background:
106 | If an input on the first row has an error message,
107 | inputs on the second row will not be properly aligned.
108 |
109 |
110 | Fix:
111 | When there are multiple inputs in a row,
112 | wrap the entire form with the class
113 | AlignInputs.
114 | This can either be applied to a new
115 | <%= "
"%> wrapping the inputs,
116 | or on a row.
117 |
118 | <% end %>
119 | <% end %>
120 |
121 | <%= row do %>
122 | <%= col do %>
123 |
Inputs (with AlignInputs wrapper)
124 | <% end %>
125 |
126 |
127 | <%= col medium: 6 do %>
128 | <%= text_input_stack f, :empty_with_error, label: "Input with Label", input: [placeholder: "Placeholder"] %>
129 | <% end %>
130 |
131 | <%= col medium: 6 do %>
132 | <%= text_input_stack f, :empty, label: "Input with Label", input: [placeholder: "Placeholder"] %>
133 | <% end %>
134 |
135 | <%= col medium: 6 do %>
136 | <%= text_input_stack f, :foo, help: "Help text goes here", input: [placeholder: "Placeholder"] %>
137 | <% end %>
138 |
139 | <%= col medium: 6 do %>
140 |
141 | <% end %>
142 |
143 | <% end %>
144 |
145 | <%= row do %>
146 | <%= col do %>
147 |
File Input
148 | <% end %>
149 |
150 | <%= col medium: 6 do %>
151 |
165 | <% end %>
166 | <% end %>
167 |
168 | <%= row do %>
169 | <%= col do %>
170 |
Input Groups (no helpers yet)
171 | <% end %>
172 |
173 | <%= col do %>
174 |
175 | Input Group
176 |
177 |
182 |
183 |
184 |
185 |
186 |
187 | Help text goes here
188 |
189 | <% end %>
190 |
191 | <%= col do %>
192 |
193 | $
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 | <% end %>
203 | <% end %>
204 |
205 | <%= row do %>
206 | <%= col do %>
207 |
Selects
208 | <% end %>
209 |
210 | <%= col medium: 6 do %>
211 | <%= select_stack f, :empty, airports, label: "Select" %>
212 | <% end %>
213 |
214 | <%= col medium: 6 do %>
215 | <%= select_stack f, :empty_with_error, airports, label: "Both Help and Error Text", help: "Help text goes here" %>
216 | <% end %>
217 | <% end %>
218 |
219 | <%= row do %>
220 | <%= col do %>
221 |
TextAreas
222 | <% end %>
223 |
224 | <%= col medium: 6 do %>
225 | <%= textarea_stack f, :foo, input: [placeholder: "Textarea"] %>
226 | <% end %>
227 |
228 | <%= col medium: 6 do %>
229 | <%= textarea f, :empty_with_error, placeholder: "Has an error", class: "rev-Textarea is-invalid-input is-invalid" %>
230 | <% end %>
231 |
232 | <%= col medium: 6 do %>
233 | <%= textarea_stack f, :foo, label: "Is a Stack" %>
234 | <% end %>
235 |
236 | <%= col medium: 6 do %>
237 | <%= textarea_stack f, :empty_with_error, label: "Has Help Text and Error", help: "Help text goes here" %>
238 | <% end %>
239 | <% end %>
240 |
241 | <%= row do %>
242 | <%= col medium: 6 do %>
243 |
244 |