├── .formatter.exs ├── .github └── workflows │ └── elixir.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── config └── config.exs ├── lib └── surface_boostrap4 │ ├── alert.ex │ ├── badge.ex │ ├── breadcrumb.ex │ ├── breadcrumb │ └── breadcrumb_item.ex │ ├── button.ex │ ├── button_group.ex │ ├── container.ex │ ├── container │ ├── col.ex │ └── row.ex │ ├── list_group.ex │ ├── list_group │ └── list_group_item.ex │ ├── nav.ex │ ├── nav │ ├── nav_item.ex │ └── nav_link.ex │ ├── progress.ex │ ├── table.ex │ └── table │ └── column.ex ├── mix.exs ├── mix.lock └── test ├── surface_bootstrap4 ├── alert_test.exs ├── badge_test.exs ├── breadcrumb_test.exs ├── button_group_test.exs ├── button_test.exs ├── container_test.exs ├── list_group_test.exs ├── nav_test.exs ├── progress_test.exs └── table_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | import_deps: [:phoenix, :surface], 4 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 5 | ] 6 | -------------------------------------------------------------------------------- /.github/workflows/elixir.yml: -------------------------------------------------------------------------------- 1 | name: Elixir CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup elixir 17 | uses: actions/setup-elixir@v1 18 | with: 19 | elixir-version: '1.10.3' # Define the elixir version [required] 20 | otp-version: '22.2' # Define the OTP version [required] 21 | - name: Install Dependencies 22 | run: mix deps.get 23 | - name: Run Tests 24 | run: mix test 25 | - name: Check unused dependencies 26 | run: mix deps.unlock --check-unused 27 | - name: Check formatted 28 | run: mix format --check-formatted -------------------------------------------------------------------------------- /.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_bootstrap4-*.tar 24 | 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2020 Thomas Schmidleithner 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 | # SurfaceBootstrap4 (WIP) 2 | 3 | ![Elixir CI](https://github.com/tschmidleithner/surface_bootstrap4/workflows/Elixir%20CI/badge.svg) 4 | 5 | A set of [Surface](https://github.com/msaraiva/surface/) components 6 | based on [Bootstrap 4](https://getbootstrap.com/) for 7 | [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view). 8 | 9 | This project is heavily inspired by [surface_bulma](https://github.com/msaraiva/surface_bulma) 10 | which is implemented and maintained by the [author](https://github.com/msaraiva) of 11 | [Surface](https://github.com/msaraiva/surface/). 12 | 13 | > **Note**: This is not a full-featured suite of components yet. 14 | 15 | ## Components 16 | 17 | More components will be added soon. Contributions are welcome! 18 | 19 | ### Layout 20 | 21 | * [x] Container (with fluid support + Row, Col) 22 | 23 | ### Components 24 | 25 | * [x] Alerts (colors, closeable) 26 | * [x] Badge 27 | * [x] Breadcrumb 28 | * [x] Buttons (colors, sizes and states) 29 | * [x] Button group 30 | * [ ] Card 31 | * [ ] Carousel 32 | * [ ] Collapse 33 | * [ ] Dropdowns 34 | * [ ] Forms 35 | * [ ] Input group 36 | * [ ] Jumbotron 37 | * [x] List group 38 | * [ ] Media object 39 | * [ ] Modal 40 | * [x] Navs (Nav, NavItem, NavLink) 41 | * [ ] Navbar 42 | * [ ] Pagination 43 | * [ ] Popovers 44 | * [x] Progress (colors, animated and striped) 45 | * [ ] Scrollspy 46 | * [ ] Spinners 47 | * [x] Tables (striped, bordered, borderless, hoverable, dark) 48 | * [ ] Toasts 49 | * [ ] Tooltips 50 | 51 | ## Example 52 | 53 | ```jsx 54 | 55 | 56 | 57 | Light badge 58 | Dark pill badge 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | A primary alert 74 | 75 | 76 | 77 | 78 | 50% 79 | 80 | 81 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | ``` 95 | 96 | More examples can be found in the [demo application](https://github.com/tschmidleithner/surface_bootstrap4_demo). 97 | 98 | ## Usage 99 | 100 | Add `surface_bootstrap4` to the list of dependencies in `mix.exs`: 101 | 102 | ```elixir 103 | def deps do 104 | [ 105 | ... 106 | {:surface_bootstrap4, github: "tschmidleithner/surface_bootstrap4"} 107 | ] 108 | end 109 | ``` 110 | 111 | To use Surface on plain Phoenix views with `~H` support, add the following import to your web file in `lib/my_app_web.ex`: 112 | 113 | ```elixir 114 | # lib/my_app_web.ex 115 | 116 | ... 117 | 118 | def view do 119 | quote do 120 | ... 121 | import Surface 122 | end 123 | end 124 | ``` 125 | 126 | You will also need to call `Surface.init/1` in the mount function: 127 | 128 | ```elixir 129 | defmodule PageLive do 130 | use Phoenix.LiveView 131 | 132 | @impl Phoenix.LiveView 133 | def mount(_params, _session, socket) do 134 | socket = Surface.init(socket) 135 | # ... 136 | {:ok, socket} 137 | end 138 | 139 | ... 140 | end 141 | ``` 142 | 143 | ```elixir 144 | defmodule NavComponent do 145 | use Phoenix.LiveComponent 146 | 147 | @impl Phoenix.LiveComponent 148 | def mount(socket) do 149 | socket = Surface.init(socket) 150 | # ... 151 | {:ok, socket} 152 | end 153 | 154 | ... 155 | end 156 | ``` 157 | 158 | To get started quickly when using Bootstrap 4 CSS styles, you can use the Bootstrap CDN 159 | and add the following line to your `root.html.leex` in the DOM head: 160 | 161 | ``` 162 | 163 | ``` 164 | 165 | ## License 166 | 167 | Copyright (c) 2020, Thomas Schmidleithner. 168 | 169 | This code is licensed under the [MIT License](LICENSE.md). 170 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :phoenix, :json_library, Jason 4 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/alert.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Alert do 2 | @moduledoc """ 3 | Bootstrap4 alert component. 4 | 5 | ## Examples 6 | ``` 7 | A simple primary alert. 8 | ``` 9 | """ 10 | 11 | use Surface.Component 12 | 13 | @doc "Additional CSS classes" 14 | prop class, :css_class 15 | 16 | @doc "The color of the alert" 17 | prop color, :string, values: ~w(primary secondary success info warning danger light dark) 18 | 19 | @doc "Triggered on click" 20 | prop toggle, :event 21 | 22 | @doc """ 23 | The content of the generated `
` element. 24 | """ 25 | slot(default) 26 | 27 | def render(assigns) do 28 | ~H""" 29 |
36 | 37 | 38 | 48 |
49 | """ 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/badge.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Badge do 2 | @moduledoc """ 3 | Bootstrap4 badge component. 4 | """ 5 | 6 | use Surface.Component 7 | 8 | @doc "The label of the badge" 9 | prop label, :string 10 | 11 | @doc "Additional CSS classes" 12 | prop class, :css_class 13 | 14 | @doc "The color of the badge" 15 | prop color, :string, values: ~w(primary secondary success info warning danger link light dark) 16 | 17 | @doc "Badges more rounded with larger border-radius" 18 | prop pill, :boolean 19 | 20 | @doc "Triggered on click" 21 | prop click, :event 22 | 23 | @doc """ 24 | The content of the generated `` element. If no content is provided, 25 | the value of property `label` is used instead. 26 | """ 27 | slot(default) 28 | 29 | def render(assigns) do 30 | ~H""" 31 | 40 | {{ @label }} 41 | 42 | """ 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/breadcrumb.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Breadcrumb do 2 | @moduledoc """ 3 | Bootstrap4 breadcrumb component. 4 | 5 | ## Examples 6 | ``` 7 | 8 | 9 | Home 10 | 11 | 12 | Library 13 | 14 | 15 | ``` 16 | """ 17 | 18 | use Surface.Component 19 | 20 | @doc "Additional CSS classes" 21 | prop class, :css_class 22 | 23 | def render(assigns) do 24 | ~H""" 25 | 35 | """ 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/breadcrumb/breadcrumb_item.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Breadcrumb.BreadcrumbItem do 2 | @moduledoc """ 3 | Bootstrap4 breadcrumb item for the breadcrumb component. 4 | """ 5 | 6 | use Surface.Component 7 | 8 | @doc """ 9 | The content of the generated breadcrumb item. 10 | """ 11 | slot default, required: true 12 | 13 | @doc "Active style" 14 | prop active, :boolean 15 | 16 | @doc "Additional CSS classes" 17 | prop class, :css_class 18 | 19 | def render(assigns) do 20 | ~H""" 21 |
  • 28 | 29 |
  • 30 | """ 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/button.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Button do 2 | @moduledoc """ 3 | Bootstrap4 button component. 4 | 5 | ## Examples 6 | ``` 7 | 8 | Primary Button 9 | 10 | ``` 11 | """ 12 | 13 | use Surface.Component 14 | 15 | @doc "The label of the button" 16 | prop label, :string 17 | 18 | @doc "Additional CSS classes" 19 | prop class, :css_class 20 | 21 | @doc "The color of the button" 22 | prop color, :string, values: ~w(primary secondary success info warning danger link) 23 | 24 | @doc "The size of button" 25 | prop size, :string, values: ~w(sm md lg) 26 | 27 | @doc "The value of the button" 28 | prop value, :string 29 | 30 | @doc "Set the button as disabled preventing the user from interacting with the control" 31 | prop disabled, :boolean 32 | 33 | @doc "Block level style" 34 | prop block, :boolean 35 | 36 | @doc "Active style" 37 | prop active, :boolean 38 | 39 | @doc "Triggered on click" 40 | prop click, :event 41 | 42 | @doc """ 43 | The content of the generated ` 66 | """ 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/button_group.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.ButtonGroup do 2 | @moduledoc """ 3 | Bootstrap4 button group component to group a series of buttons together. 4 | 5 | ## Examples 6 | ``` 7 | 8 | 9 | Left 10 | 11 | 12 | Middle 13 | 14 | 15 | Right 16 | 17 | 18 | ``` 19 | """ 20 | 21 | use Surface.Component 22 | 23 | @doc "The size of buttons within the button group" 24 | prop size, :string, values: ~w(sm md lg) 25 | 26 | @doc "Vertical stack buttons" 27 | prop vertical, :boolean, default: false 28 | 29 | @doc "Additional CSS classes" 30 | prop class, :css_class 31 | 32 | def render(assigns) do 33 | ~H""" 34 |
    42 | 43 |
    44 | """ 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/container.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Container do 2 | @moduledoc """ 3 | Bootstrap4 container. 4 | 5 | ## Examples 6 | ``` 7 | 8 | 9 | 10 | Container -> Row -> Column 11 | 12 | 13 | 14 | ``` 15 | """ 16 | 17 | use Surface.Component 18 | 19 | @doc "The type of the container" 20 | prop fluid, :boolean, default: false 21 | 22 | @doc "Additional CSS classes" 23 | prop class, :css_class 24 | 25 | @doc """ 26 | The content of the generated container. 27 | """ 28 | slot default, required: true 29 | 30 | def render(assigns) do 31 | ~H""" 32 |
    39 | 40 |
    41 | """ 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/container/col.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Container.Col do 2 | @moduledoc """ 3 | Bootstrap4 col for the containers row component. 4 | """ 5 | 6 | use Surface.Component 7 | 8 | @doc "The size of column" 9 | prop size, :integer 10 | 11 | @doc "Responsive behaviour for xs of the column" 12 | prop xs, :integer 13 | 14 | @doc "Responsive behaviour for sm of the column" 15 | prop sm, :integer 16 | 17 | @doc "Responsive behaviour for md of the column" 18 | prop md, :integer 19 | 20 | @doc "Responsive behaviour for lg of the column" 21 | prop lg, :integer 22 | 23 | @doc "Responsive behaviour for xl of the column" 24 | prop xl, :integer 25 | 26 | @doc "Additional CSS classes" 27 | prop class, :css_class 28 | 29 | @doc """ 30 | The content of the generated column. 31 | """ 32 | slot default, required: true 33 | 34 | def render(assigns) do 35 | ~H""" 36 |
    48 | 49 |
    50 | """ 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/container/row.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Container.Row do 2 | @moduledoc """ 3 | Bootstrap4 row for the container component. 4 | """ 5 | 6 | use Surface.Component 7 | 8 | @doc "The type of the container" 9 | prop no_gutters, :boolean, default: false 10 | 11 | @doc "Additional CSS classes" 12 | prop class, :css_class 13 | 14 | @doc """ 15 | The content of the generated row. 16 | """ 17 | slot default, required: true 18 | 19 | def render(assigns) do 20 | ~H""" 21 |
    28 | 29 |
    30 | """ 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/list_group.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.ListGroup do 2 | @moduledoc """ 3 | Bootstrap4 list group component. 4 | 5 | ## Examples 6 | ``` 7 | 8 | 9 | Cras justo odio 10 | 11 | 12 | Dapibus ac facilisis in 13 | 14 | 15 | Morbi leo risus 16 | 17 | 18 | ``` 19 | """ 20 | 21 | use Surface.Component 22 | 23 | @doc "Flush style" 24 | prop flush, :boolean 25 | 26 | @doc "Horizontal style" 27 | prop horizontal, :boolean 28 | 29 | @doc "Additional CSS classes" 30 | prop class, :css_class 31 | 32 | def render(assigns) do 33 | ~H""" 34 | 44 | """ 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/list_group/list_group_item.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.ListGroup.ListGroupItem do 2 | @moduledoc """ 3 | Bootstrap4 list group item for the list group component. 4 | """ 5 | 6 | use Surface.Component 7 | 8 | @doc """ 9 | The content of the generated list group item. 10 | """ 11 | slot default, required: true 12 | 13 | @doc "Active style" 14 | prop active, :boolean 15 | 16 | @doc "Disabled style" 17 | prop disabled, :boolean 18 | 19 | @doc "Additional CSS classes" 20 | prop class, :css_class 21 | 22 | def render(assigns) do 23 | ~H""" 24 |
  • 32 | 33 |
  • 34 | """ 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/nav.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Nav do 2 | @moduledoc """ 3 | Bootstrap4 navigation component to further wrap children such as 4 | `SurfaceBootstrap4.Nav.NavItem` and `SurfaceBootstrap4.Nav.NavLink`. 5 | 6 | ## Examples 7 | ``` 8 | 9 | 10 | Navigation item 11 | 12 | 13 | ``` 14 | """ 15 | 16 | use Surface.Component 17 | 18 | @doc "Vertical stack navigation" 19 | prop vertical, :boolean 20 | 21 | @doc "Tabbed navigation" 22 | prop tabs, :boolean 23 | 24 | @doc "Navigation with pills" 25 | prop pills, :boolean 26 | 27 | @doc "Additional CSS classes" 28 | prop class, :css_class 29 | 30 | def render(assigns) do 31 | ~H""" 32 | 43 | """ 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/nav/nav_item.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Nav.NavItem do 2 | @moduledoc """ 3 | Bootstrap4 navigation item for the navigation component. 4 | """ 5 | 6 | use Surface.Component 7 | 8 | @doc """ 9 | The content of the generated navigation item. 10 | """ 11 | slot default, required: true 12 | 13 | @doc "Additional CSS classes" 14 | prop class, :css_class 15 | 16 | def render(assigns) do 17 | ~H""" 18 |
  • 24 | 25 |
  • 26 | """ 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/nav/nav_link.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Nav.NavLink do 2 | @moduledoc """ 3 | Bootstrap4 navigation link for the navigation item component. 4 | """ 5 | 6 | use Surface.Component 7 | 8 | @doc "Additional CSS classes" 9 | prop class, :css_class 10 | 11 | @doc "Active style" 12 | prop active, :boolean 13 | 14 | @doc "Set the navigation link as disabled" 15 | prop disabled, :boolean 16 | 17 | @doc "Place to link to" 18 | prop to, :string, required: true 19 | 20 | @doc "Triggered on click" 21 | prop click, :event 22 | 23 | @doc """ 24 | The label for the generated navigation link, if no content (default slot) is 25 | provided. 26 | """ 27 | prop label, :string 28 | 29 | def render(assigns) do 30 | ~H""" 31 | 41 | {{ @label }} 42 | 43 | """ 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/progress.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Progress do 2 | @moduledoc """ 3 | Bootstrap4 progress component. 4 | 5 | ## Examples 6 | ``` 7 | 50% 8 | ``` 9 | """ 10 | 11 | use Surface.Component 12 | 13 | @doc "The label of the button" 14 | prop label, :string 15 | 16 | @doc "The value of the progress" 17 | prop value, :integer, default: 0 18 | 19 | @doc "Maximum value of the progress" 20 | prop max_value, :integer, default: 100 21 | 22 | @doc "Additional CSS classes for the outer progress div" 23 | prop class, :css_class 24 | 25 | @doc "Additional CSS classes for the inner bar div" 26 | prop bar_class, :css_class 27 | 28 | @doc "The color of the button" 29 | prop color, :string, values: ~w(primary secondary success info warning danger) 30 | 31 | @doc "Stripe style" 32 | prop striped, :boolean 33 | 34 | @doc "Animated style" 35 | prop animated, :boolean 36 | 37 | @doc """ 38 | The content of the generated progress element. If no content is provided, 39 | the value of property `label` is used instead. 40 | """ 41 | slot(default) 42 | 43 | def render(assigns) do 44 | percent = assigns.value / assigns.max_value * 100 45 | 46 | ~H""" 47 |
    51 |
    65 | {{ @label }} 66 |
    67 |
    68 | """ 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/table.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Table do 2 | @moduledoc """ 3 | Bootstrap4 table component. 4 | 5 | A table can be created by setting a source `data` to it and defining 6 | columns using the `SurfaceBootstrap4.Table.Column` component. 7 | """ 8 | 9 | use Surface.Component 10 | 11 | @doc "The data that populates the table" 12 | prop data, :list, required: true 13 | 14 | @doc "Additional CSS classes" 15 | prop class, :css_class 16 | 17 | @doc "The size of the table" 18 | prop size, :string, values: ~w(sm md lg) 19 | 20 | @doc "Add borders to all the cells" 21 | prop bordered, :boolean, default: false 22 | 23 | @doc "Add borderless to all the cells" 24 | prop borderless, :boolean, default: false 25 | 26 | @doc "Add stripes to the table" 27 | prop striped, :boolean, default: false 28 | 29 | @doc "Add hovers to the table" 30 | prop hover, :boolean, default: false 31 | 32 | @doc "Invert colors of the table to dark background and light text" 33 | prop dark, :boolean, default: false 34 | 35 | @doc """ 36 | A function that returns a class for the item's underlying `` 37 | element. The function receives the item and index related to 38 | the row. 39 | """ 40 | prop rowClass, :fun 41 | 42 | @doc "The columns of the table" 43 | slot cols, props: [item: ^data], required: true 44 | 45 | def render(assigns) do 46 | ~H""" 47 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | 68 | 71 | 72 | 73 |
    60 | {{ col.label }} 61 |
    69 | 70 |
    74 | """ 75 | end 76 | 77 | defp row_class_fun(nil), do: fn _, _ -> "" end 78 | defp row_class_fun(rowClass), do: rowClass 79 | end 80 | -------------------------------------------------------------------------------- /lib/surface_boostrap4/table/column.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.Table.Column do 2 | @moduledoc """ 3 | Defines a column for the parent table component. 4 | The column instance is automatically added to the table's 5 | `cols` slot. 6 | """ 7 | 8 | use Surface.Component, slot: "cols" 9 | 10 | @doc "Column header text" 11 | prop label, :string, required: true 12 | end 13 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.MixProject do 2 | use Mix.Project 3 | 4 | @source_url "https://github.com/tschmidleithner/surface_bootstrap4" 5 | @version "0.1.0" 6 | 7 | def project do 8 | [ 9 | app: :surface_bootstrap4, 10 | version: @version, 11 | elixir: "~> 1.10", 12 | start_permanent: Mix.env() == :prod, 13 | compilers: [:phoenix] ++ Mix.compilers(), 14 | description: description(), 15 | package: package(), 16 | deps: deps(), 17 | source_url: @source_url 18 | ] 19 | end 20 | 21 | def application do 22 | [ 23 | extra_applications: [:logger] 24 | ] 25 | end 26 | 27 | defp deps do 28 | [ 29 | {:jason, "~> 1.0"}, 30 | {:floki, "~> 0.29", only: :test}, 31 | {:surface, "~> 0.1"}, 32 | {:ex_parameterized, "~> 1.3.7", only: :test} 33 | ] 34 | end 35 | 36 | defp description do 37 | "A set of Surface components based on Bootstrap 4 for Phoenix LiveView." 38 | end 39 | 40 | defp package do 41 | [ 42 | maintainers: ["Thomas Schmidleithner"], 43 | licenses: ["MIT"], 44 | links: %{"GitHub" => @source_url} 45 | ] 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "earmark": {:hex, :earmark, "1.4.13", "2c6ce9768fc9fdbf4046f457e207df6360ee6c91ee1ecb8e9a139f96a4289d91", [:mix], [{:earmark_parser, ">= 1.4.12", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "a0cf3ed88ef2b1964df408889b5ecb886d1a048edde53497fc935ccd15af3403"}, 3 | "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, 4 | "ex_parameterized": {:hex, :ex_parameterized, "1.3.7", "801f85fc4651cb51f11b9835864c6ed8c5e5d79b1253506b5bb5421e8ab2f050", [:mix], [], "hexpm", "1fb0dc4aa9e8c12ae23806d03bcd64a5a0fc9cd3f4c5602ba72561c9b54a625c"}, 5 | "floki": {:hex, :floki, "0.29.0", "b1710d8c93a2f860dc2d7adc390dd808dc2fb8f78ee562304457b75f4c640881", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "008585ce64b9f74c07d32958ec9866f4b8a124bf4da1e2941b28e41384edaaad"}, 6 | "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, 7 | "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, 8 | "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, 9 | "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, 10 | "phoenix": {:hex, :phoenix, "1.5.7", "2923bb3af924f184459fe4fa4b100bd25fa6468e69b2803dfae82698269aa5e0", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "774cd64417c5a3788414fdbb2be2eb9bcd0c048d9e6ad11a0c1fd67b7c0d0978"}, 11 | "phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"}, 12 | "phoenix_live_view": {:hex, :phoenix_live_view, "0.15.1", "052ecf12c7bc19d5d4942d3a9503f23c5c6da879d71c76080f118a7935622471", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 0.5", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "040c285d3e22757157a2ade6995ad96816214a907d170f067686aeb1fc018439"}, 13 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, 14 | "plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"}, 15 | "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, 16 | "surface": {:hex, :surface, "0.1.1", "812249c5832d1b36051101a3fe68eac374758cc4437652bf9bf2cb14ab052fb7", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "08b2c00ad1a3c6d0f60fa05bda43aa90b112f71e7989870a6e756670fdc1698c"}, 17 | "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, 18 | } 19 | -------------------------------------------------------------------------------- /test/surface_bootstrap4/alert_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.AlertTest do 2 | use ExUnit.Case, async: true 3 | use ExUnit.Parameterized 4 | 5 | alias SurfaceBootstrap4.Alert, warn: false 6 | import ComponentTestHelper 7 | 8 | test "creates a
    with class alert" do 9 | code = ~S(alert) 10 | 11 | expected = """ 12 |
    13 | alert 14 |
    15 | """ 16 | 17 | assert render_live(code) =~ expected 18 | end 19 | 20 | test "creates a
    with class alert, alert-primary and additional CSS classes" do 21 | code = ~S(With padding and margin) 22 | 23 | expected = """ 24 |
    25 | With padding and margin 26 |
    27 | """ 28 | 29 | assert render_live(code) =~ expected 30 | end 31 | 32 | test_with_params "prop color", &assert_regex/2 do 33 | [ 34 | primary: { 35 | ~S(), 36 | ~r/class="(.*)alert-primary(.*)"/ 37 | }, 38 | secondary: { 39 | ~S(), 40 | ~r/class="(.*)alert-secondary(.*)"/ 41 | }, 42 | success: { 43 | ~S(), 44 | ~r/class="(.*)alert-success(.*)"/ 45 | }, 46 | info: { 47 | ~S(), 48 | ~r/class="(.*)alert-info(.*)"/ 49 | }, 50 | warning: { 51 | ~S(), 52 | ~r/class="(.*)alert-warning(.*)"/ 53 | }, 54 | danger: { 55 | ~S(), 56 | ~r/class="(.*)alert-danger(.*)"/ 57 | }, 58 | light: { 59 | ~S(), 60 | ~r/class="(.*)alert-light(.*)"/ 61 | }, 62 | dark: { 63 | ~S(), 64 | ~r/class="(.*)alert-dark(.*)"/ 65 | } 66 | ] 67 | end 68 | 69 | defp assert_regex(component, expected_regex) do 70 | assert render_live(component) =~ expected_regex 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /test/surface_bootstrap4/badge_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.BadgeTest do 2 | use ExUnit.Case, async: true 3 | use ExUnit.Parameterized 4 | 5 | alias SurfaceBootstrap4.Badge, warn: false 6 | import ComponentTestHelper 7 | 8 | test "creates a with class badge" do 9 | expected = """ 10 | 11 | A badge 12 | 13 | """ 14 | 15 | assert_component(~S(A badge), expected) 16 | end 17 | 18 | test "creates a with class badge, badge-primary and additional CSS classes" do 19 | expected = """ 20 | 21 | With padding and margin 22 | 23 | """ 24 | 25 | assert_component( 26 | ~S(With padding and margin), 27 | expected 28 | ) 29 | end 30 | 31 | test_with_params "prop color", &render_and_assert_badge/2 do 32 | [ 33 | primary: { 34 | ~S(primary), 35 | "primary" 36 | }, 37 | secondary: { 38 | ~S(secondary), 39 | "secondary" 40 | }, 41 | success: { 42 | ~S(success), 43 | "success" 44 | }, 45 | info: { 46 | ~S(info), 47 | "info" 48 | }, 49 | warning: { 50 | ~S(warning), 51 | "warning" 52 | }, 53 | danger: { 54 | ~S(danger), 55 | "danger" 56 | }, 57 | link: { 58 | ~S(link), 59 | "link" 60 | }, 61 | light: { 62 | ~S(light), 63 | "light" 64 | }, 65 | dark: { 66 | ~S(dark), 67 | "dark" 68 | } 69 | ] 70 | end 71 | 72 | test_with_params "prop pill", &assert_component/2 do 73 | [ 74 | pill: { 75 | ~S(pill), 76 | """ 77 | 78 | pill 79 | 80 | """ 81 | }, 82 | primary_pill: { 83 | ~S(primary pill), 84 | """ 85 | 86 | primary pill 87 | 88 | """ 89 | } 90 | ] 91 | end 92 | 93 | defp render_and_assert_badge(component, expected_color) do 94 | expected = """ 95 | 96 | #{expected_color} 97 | 98 | """ 99 | 100 | assert render_live(component) =~ expected 101 | end 102 | 103 | defp assert_component(component, expected) do 104 | assert render_live(component) =~ expected 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /test/surface_bootstrap4/breadcrumb_test.exs: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBootstrap4.BreadcrumbTest do 2 | use ExUnit.Case, async: true 3 | use ExUnit.Parameterized 4 | doctest SurfaceBootstrap4.Breadcrumb 5 | 6 | alias SurfaceBootstrap4.Breadcrumb, warn: false 7 | alias SurfaceBootstrap4.Breadcrumb.BreadcrumbItem, warn: false 8 | alias Surface.Components.Link, warn: false 9 | import ComponentTestHelper 10 | 11 | test "creates a