├── .formatter.exs
├── .github
└── workflows
│ └── elixir.yml
├── .gitignore
├── .vscode
└── settings.json
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── config
├── config.exs
└── contexts.exs
├── dev.exs
├── lib
├── surface_bulma.ex
└── surface_bulma
│ ├── button.ex
│ ├── button_group.ex
│ ├── buttons.ex
│ ├── card.ex
│ ├── card
│ ├── footer-item.ex
│ ├── footer.ex
│ ├── header-icon.ex
│ ├── header-title.ex
│ ├── header.ex
│ └── image.ex
│ ├── collapsible.ex
│ ├── component.ex
│ ├── delete.ex
│ ├── divider.ex
│ ├── dropdown.ex
│ ├── dropdown
│ └── trigger.ex
│ ├── form.ex
│ ├── form
│ ├── checkbox.ex
│ ├── color_input.ex
│ ├── date_input.ex
│ ├── date_select.ex
│ ├── date_time_local_input.ex
│ ├── datetime_select.ex
│ ├── email_input.ex
│ ├── field_base.ex
│ ├── field_wrapper.ex
│ ├── file_input.ex
│ ├── hidden_input.ex
│ ├── hidden_inputs.ex
│ ├── horizontal_control_group.ex
│ ├── input_addon_base.ex
│ ├── input_base.ex
│ ├── input_icon_base.ex
│ ├── input_wrapper.ex
│ ├── inputs.ex
│ ├── label.ex
│ ├── number_input.ex
│ ├── password_input.ex
│ ├── radio_button.ex
│ ├── range_input.ex
│ ├── reset.ex
│ ├── search_input.ex
│ ├── select.ex
│ ├── submit.ex
│ ├── telephone_input.ex
│ ├── text_area.ex
│ ├── text_input.ex
│ ├── text_input_base.ex
│ ├── time_input.ex
│ ├── time_select.ex
│ ├── url_input.ex
│ └── utils.ex
│ ├── icon.ex
│ ├── icon
│ └── font_awesome.ex
│ ├── item.ex
│ ├── level.ex
│ ├── link.ex
│ ├── message.ex
│ ├── message
│ └── header.ex
│ ├── modal.ex
│ ├── modal
│ ├── card.ex
│ ├── footer.ex
│ └── header.ex
│ ├── navbar.ex
│ ├── navbar
│ ├── brand.ex
│ ├── dropdown.ex
│ ├── end.ex
│ └── start.ex
│ ├── panel.ex
│ ├── panel
│ ├── header.ex
│ └── tab.ex
│ ├── static_table.ex
│ ├── sub_title.ex
│ ├── tab_utils.ex
│ ├── table.ex
│ ├── table
│ └── column.ex
│ ├── tabs.ex
│ ├── tabs
│ └── tab.ex
│ ├── tabs_row.ex
│ ├── tag.ex
│ └── title.ex
├── mix.exs
├── mix.lock
├── priv
├── catalogue
│ ├── assets
│ │ ├── bulma.min.css
│ │ └── catalogue
│ │ │ ├── app.css
│ │ │ └── app.js
│ ├── button
│ │ ├── example01.ex
│ │ ├── example02.ex
│ │ ├── example03.ex
│ │ ├── example04.ex
│ │ └── playground.ex
│ ├── card
│ │ └── example01.ex
│ ├── catalogue.ex
│ ├── dropdown
│ │ ├── example01.ex
│ │ ├── example02.ex
│ │ ├── example03.ex
│ │ ├── example04.ex
│ │ ├── example05.ex
│ │ └── playground.ex
│ ├── form
│ │ ├── example01.ex
│ │ ├── example02.ex
│ │ ├── example03.ex
│ │ ├── example04.ex
│ │ └── sample_model.ex
│ ├── level
│ │ └── example01.ex
│ ├── message
│ │ ├── example01.ex
│ │ ├── example02.ex
│ │ ├── example03.ex
│ │ └── playground.ex
│ ├── navbar
│ │ ├── example01.ex
│ │ ├── example02.ex
│ │ └── playground.ex
│ ├── panel
│ │ ├── example01.ex
│ │ └── playground.ex
│ ├── table
│ │ └── example01.ex
│ └── tabs
│ │ └── example01.ex
└── font-awesome-icons.txt
└── test
├── support
└── conn_case.ex
├── surface_bulma
├── button_test.exs
├── card_test.exs
├── delete_test.exs
├── dropdown_test.exs
├── form
│ ├── checkbox_test.exs
│ ├── color_input_test.exs
│ ├── date_input_test.exs
│ ├── date_select_test.exs
│ ├── datetime_local_input_test.exs
│ ├── datetime_select_test.exs
│ ├── email_input_test.exs
│ ├── file_input_test.exs
│ ├── hidden_input_test.exs
│ ├── input_wrapper_test.exs
│ ├── label_test.exs
│ ├── multiple_select_test.exs
│ ├── number_input_test.exs
│ ├── password_input_test.exs
│ ├── radio_button_test.exs
│ ├── range_input_test.exs
│ ├── reset_test.exs
│ ├── search_input_test.exs
│ ├── select_test.exs
│ ├── submit_test.exs
│ ├── telephone_input_test.exs
│ ├── textarea_test.exs
│ ├── time_input_test.exs
│ ├── time_select_test.exs
│ └── url_input_test.exs
├── form_test.exs
├── icon_test.exs
├── item_test.exs
├── live_link_test.exs
├── message_test.exs
├── modal_test.exs
├── navbar_test.exs
├── panel_test.exs
├── table_test.exs
└── tag_test.exs
└── test_helper.exs
/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | import_deps: [:phoenix, :surface, :ecto],
3 | inputs: [
4 | "{mix,.formatter}.exs",
5 | "{config,lib,test}/**/*.{ex,exs,sface}",
6 | "priv/catalogue/**/*.{ex,sface}",
7 | "test/**/*.exs"
8 | ],
9 | plugins: [Surface.Formatter.Plugin]
10 | ]
11 |
--------------------------------------------------------------------------------
/.github/workflows/elixir.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 |
6 | name: Elixir CI
7 |
8 | on:
9 | push:
10 | branches: [ "main" ]
11 | pull_request:
12 | branches: [ "main" ]
13 |
14 | permissions:
15 | contents: read
16 |
17 | jobs:
18 | build:
19 |
20 | name: Build and test
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - uses: actions/checkout@v3
25 | - name: Set up Elixir
26 | uses: erlef/setup-beam@988e02bfe678367a02564f65ca2e37726dc0268f
27 | with:
28 | elixir-version: '1.12.3' # Define the elixir version [required]
29 | otp-version: '24.1' # Define the OTP version [required]
30 | - name: Restore dependencies cache
31 | uses: actions/cache@v3
32 | with:
33 | path: deps
34 | key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
35 | restore-keys: ${{ runner.os }}-mix-
36 | - name: Install dependencies
37 | run: mix deps.get
38 | - name: Run tests
39 | run: mix test
40 |
--------------------------------------------------------------------------------
/.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 third-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 | surface_bulma-*.tar
24 |
25 | #vscode settings
26 | /.vscode/
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "elixirLS.projectDir": ""
3 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.2.1
2 | The Table component supports a list of keys in the `sorted_by` prop, which will fetch try to automatically fetch the nested data based on the keys. Datatypes that are supported are: Map and List. If the key is a string or atom, then `Map.get/2` will be used, otherwise `Enum.at`.
3 |
4 | Fixed a bug that prevented the Table component from updating when the `data` prop was changed.
5 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright (c) 2020 Marlus Saraiva
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SurfaceBulma
2 |
3 | A set of simple [Surface](https://github.com/msaraiva/surface/) components
4 | based on [Bulma](https://bulma.io/).
5 |
6 | **Documentation** and **live examples** can be found at http://surface-demo.msaraiva.io/uicomponents.
7 |
8 | > **Note**: This is not a full-featured suite of components yet. Most of
9 | the functionality we have so far is based on the requirements we had
10 | to build Surface's [website](http://surface-demo.msaraiva.io).
11 |
12 | ## Components
13 |
14 | * [Button](http://surface-demo.msaraiva.io/uicomponents/Button) -
15 | The classic button, in different colors, sizes, and states.
16 | (Thanks [Tiago Morais](https://github.com/tiagoefmoraes)).
17 | * [Table](http://surface-demo.msaraiva.io/uicomponents/Table) -
18 | The inevitable HTML table.
19 | * [Tabs](http://surface-demo.msaraiva.io/uicomponents/Tabs) -
20 | A simple horizontal navigation tabs component.
21 | * Form inputs
22 | Most of the Form inputs that exist in Surface are now available.
23 | * Panel component
24 | * Navbar
25 | * Dialog (Soon!)
26 | * Card (Thanks [Justin M Morgan](https://github.com/justin-m-morgan))
27 | * Dropdown (Thanks [Justin M Morgan](https://github.com/justin-m-morgan))
28 | * Message (Thanks [Justin M Morgan](https://github.com/justin-m-morgan))
29 |
30 | More components will be added soon. Contributions are welcome!
31 |
32 | ## Example
33 |
34 | ```jsx
35 |
36 |
37 | {{ album.name }}
38 |
39 |
40 | {{ album.released }}
41 |
42 |
43 | {{ album.artist }}
44 |
45 |
46 | ```
47 |
48 | ## Usage
49 |
50 | Add `surface_bulma` to the list of dependencies in `mix.exs`:
51 |
52 | ```elixir
53 | def deps do
54 | [
55 | ...
56 | {:surface_bulma, "~> 0.4.0"}
57 | ]
58 | end
59 | ```
60 |
61 | Add the following line to your `config/config.exs`:
62 | ```elixir
63 | ...
64 | import_config "../deps/surface_bulma/config/contexts.exs"
65 | ...
66 | ```
67 |
68 | To use bulma's CSS styles, choose one of the following methods:
69 |
70 | ### 1. Using CDN or downloading files
71 |
72 | Add the following line to your `layout_view.ex`:
73 |
74 | ```
75 |
76 | ```
77 |
78 | Or download the `.css` file and manually add it to your `priv/static/css` folder.
79 | In this case, add the following line to your `layout_view.ex`:
80 |
81 | ```
82 |
83 | ```
84 |
85 | ### 2. NPM or Yarn
86 |
87 | Add `bulma` to the list of dependencies in `assets/package.json`:
88 |
89 | ```
90 | "dependencies": {
91 | ...
92 | "bulma": "0.8.0"
93 | }
94 | ```
95 |
96 | ## License
97 |
98 | Copyright (c) 2020, Marlus Saraiva.
99 |
100 | Surface source code is licensed under the [MIT License](LICENSE.md).
101 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | config :phoenix, :json_library, Jason
4 |
5 | import_config("contexts.exs")
6 |
7 | config :esbuild,
8 | version: "0.13.8",
9 | catalogue: [
10 | args:
11 | ~w(../deps/surface_catalogue/assets/js/app.js --bundle --target=es2016 --minify --outdir=../priv/static/assets/),
12 | cd: Path.expand("../assets", __DIR__),
13 | env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
14 | ]
15 |
16 | config :surface_catalogue, :assets_path, "/assets"
17 |
--------------------------------------------------------------------------------
/config/contexts.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | config :surface,
4 | components: [
5 | {Surface.Components.Form.Field, [default_class: "field"]},
6 | {Surface.Components.Form.Label, [default_class: "label"]},
7 | {SurfaceBulma.Collapsible, propagate_context_to_slots: true},
8 | {SurfaceBulma.Dropdown, propagate_context_to_slots: true},
9 | {SurfaceBulma.Navbar, propagate_context_to_slots: true},
10 | {SurfaceBulma.Navbar.Brand, propagate_context_to_slots: true},
11 | {SurfaceBulma.Navbar.Dropdown, propagate_context_to_slots: true},
12 | {SurfaceBulma.Form, propagate_context_to_slots: true},
13 | {SurfaceBulma.Form.Checkbox, propagate_context_to_slots: true},
14 | {SurfaceBulma.Form.Input, propagate_context_to_slots: true},
15 | {SurfaceBulma.Form.TextInput, propagate_context_to_slots: true},
16 | {SurfaceBulma.Form.PasswordInput, propagate_context_to_slots: true},
17 | {SurfaceBulma.Form.InputWrapper, propagate_context_to_slots: true},
18 | {SurfaceBulma.Form.InputWrapperTest.Slot, propagate_context_to_slots: true},
19 | {SurfaceBulma.Form.InputWrapper, :render_left_addon, propagate_context_to_slots: true},
20 | {SurfaceBulma.Form.InputWrapper, :render_right_addon, propagate_context_to_slots: true},
21 | {SurfaceBulma.Form.FileInput, propagate_context_to_slots: true},
22 | {SurfaceBulma.Form.Select, propagate_context_to_slots: true},
23 | {SurfaceBulma.Panel, propagate_context_to_slots: true},
24 | {SurfaceBulma.Panel.Tab, propagate_context_to_slots: true},
25 | {SurfaceBulma.Panel.Tab.TabItem, propagate_context_to_slots: true}
26 | ]
27 |
--------------------------------------------------------------------------------
/dev.exs:
--------------------------------------------------------------------------------
1 | # iex -S mix dev
2 |
3 | Logger.configure(level: :debug)
4 |
5 | defmodule Surface.Catalogue.ErrorView do
6 | use Phoenix.Template,
7 | root: "deps/surface_catalogue/lib/surface/catalogue/templates/",
8 | namespace: Surface.Catalogue
9 | end
10 |
11 | defmodule Surface.Catalogue.Server.Router do
12 | use Phoenix.Router
13 | import Surface.Catalogue.Router
14 |
15 | surface_catalogue("/catalogue")
16 | end
17 |
18 | # Start the catalogue server
19 | Surface.Catalogue.Server.start(
20 | watchers: [
21 | esbuild: {Esbuild, :install_and_run, [:catalogue, ~w(--sourcemap=inline --watch)]}
22 | ],
23 | live_reload: [
24 | patterns: [
25 | ~r"lib/surface_bulma/.*(ex)$",
26 | ~r"priv/catalogue/.*(ex)$"
27 | ]
28 | ],
29 | )
30 |
--------------------------------------------------------------------------------
/lib/surface_bulma.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBulma do
2 | @moduledoc """
3 | This component library is a work in progress that aims to implement the Bulma framework is Surface.
4 | """
5 | defmodule SizeProp do
6 | defmacro __using__(component_doc_name) do
7 | component_doc_name =
8 | if component_doc_name != [] do
9 | component_doc_name
10 | else
11 | Module.split(__CALLER__.module) |> List.last() |> String.downcase()
12 | end
13 |
14 | quote do
15 | @doc "The color of the #{unquote(component_doc_name)}."
16 | prop size, :string, values: ~w(small normal medium large)
17 | end
18 | end
19 | end
20 |
21 | defmodule ColorProp do
22 | @moduledoc false
23 | defmacro __using__(component_doc_name) do
24 | component_doc_name =
25 | if component_doc_name != [] do
26 | component_doc_name
27 | else
28 | Module.split(__CALLER__.module) |> List.last() |> String.downcase()
29 | end
30 |
31 | quote do
32 | @doc "The color of the #{unquote(component_doc_name)}."
33 | prop color, :string,
34 | values: ~w(white black light dark primary link info success warning danger)
35 | end
36 | end
37 | end
38 |
39 | defmodule ClassProp do
40 | @moduledoc false
41 | defmacro __using__(opts \\ []) do
42 | default = opts[:default]
43 |
44 | doc =
45 | opts[:doc] ||
46 | "Additional CSS classes#{(default && ", default is: #{inspect(default)}") || ""}"
47 |
48 | quote bind_quoted: [default: default, doc: doc] do
49 | import SurfaceBulma, only: [classes: 1, classes: 2]
50 | @doc doc
51 | prop class, :css_class, default: default
52 | end
53 | end
54 | end
55 |
56 | defmodule ContextClass do
57 | @moduledoc false
58 | defmacro __using__(_) do
59 | quote do
60 | import SurfaceBulma, only: [classes: 1, classes: 2]
61 | @doc false
62 | data context_class, :css_class, from_context: {unquote(__CALLER__.module), :context_class}
63 | end
64 | end
65 | end
66 |
67 | def classes(assigns, classes \\ []) do
68 | context_class = normalize_class(assigns[:context_class])
69 | class = normalize_class(assigns[:class])
70 | classes = normalize_class(classes)
71 | class ++ context_class ++ classes
72 | end
73 |
74 | defp normalize_class(class) do
75 | cond do
76 | is_binary(class) ->
77 | [{class, true}]
78 |
79 | is_list(class) ->
80 | class
81 |
82 | true ->
83 | []
84 | end
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/lib/surface_bulma/button.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBulma.Button do
2 | @moduledoc """
3 | The classic **button**, in different colors, sizes, and states
4 | """
5 | alias SurfaceBulma.{ClassProp, ColorProp, SizeProp}
6 | use Surface.Component
7 |
8 | @doc """
9 | The button type, defaults to "button", mainly used for instances like modal X to close style buttons
10 | where you don't want to set a type at all. Setting to nil makes button have no type.
11 | """
12 | prop type, :string, default: "button"
13 |
14 | @doc "The label of the button, when no content (default slot) is provided"
15 | prop label, :string
16 |
17 | @doc "The aria label for the button"
18 | prop aria_label, :string
19 |
20 | use ColorProp
21 |
22 | use SizeProp
23 |
24 | @doc "Is link?"
25 | prop link, :boolean
26 |
27 | @doc "The value for the button"
28 | prop value, :string
29 |
30 | @doc "Button is expanded (full-width)"
31 | prop expand, :boolean
32 |
33 | @doc "Set the button as disabled preventing the user from interacting with the control"
34 | prop disabled, :boolean
35 |
36 | @doc "Outlined style"
37 | prop outlined, :boolean
38 |
39 | @doc "Inverted style"
40 | prop inverted, :boolean
41 |
42 | @doc "Rounded style"
43 | prop rounded, :boolean
44 |
45 | @doc "Light style"
46 | prop light, :boolean
47 |
48 | @doc "Hovered style"
49 | prop hovered, :boolean
50 |
51 | @doc "Focused style"
52 | prop focused, :boolean
53 |
54 | @doc "Active style"
55 | prop active, :boolean
56 |
57 | @doc "Selected style"
58 | prop selected, :boolean
59 |
60 | @doc "Static style"
61 | prop static, :boolean
62 |
63 | @doc "Loading state"
64 | prop loading, :boolean
65 |
66 | @doc "Triggered on click"
67 | prop click, :event
68 |
69 | use ClassProp, default: "button"
70 |
71 | @doc """
72 | Additional attributes to add onto the generated element
73 | """
74 | prop opts, :keyword, default: []
75 |
76 | @doc """
77 | Whether or not this button is used as an addon to a form field.
78 | """
79 | prop addon, :boolean, default: false
80 |
81 | @doc """
82 | The content of the generated `