├── .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 ` 132 | """ 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /lib/surface_bulma/button_group.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.ButtonGroup do 2 | @moduledoc """ 3 | Group of buttons. 4 | 5 | https://bulma.io/documentation/elements/button/#list-of-buttons 6 | """ 7 | use Surface.Component 8 | 9 | @doc "Are buttons attached to each other?" 10 | prop attached, :boolean 11 | 12 | @doc "Aligned left, right or center" 13 | prop aligned, :string, values: ~w(left centered right) 14 | 15 | slot default 16 | 17 | def render(assigns) do 18 | ~F""" 19 |
20 | <#slot /> 21 |
22 | """ 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/surface_bulma/buttons.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Buttons do 2 | use Surface.Component, slot: "buttons" 3 | 4 | slot default 5 | 6 | def render(assigns) do 7 | ~F""" 8 |
9 | {#for item <- @default} 10 | <#slot {item} /> 11 | {/for} 12 |
13 | """ 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/surface_bulma/card.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Card do 2 | use Surface.Component 3 | 4 | @moduledoc """ 5 | An all-around flexible and composable component. 6 | 7 | [Bulma Docs - Card](https://bulma.io/documentation/components/card/) 8 | """ 9 | 10 | use SurfaceBulma.ClassProp 11 | 12 | @doc "Custom options forwarded to container element" 13 | prop attrs, :map, default: %{} 14 | 15 | @doc "Multi-purpose container for any other element" 16 | slot default, required: true 17 | 18 | @doc "A horizontal bar with a shadow" 19 | slot header 20 | 21 | @doc "A full-width container for a responsive image" 22 | slot image 23 | 24 | @doc "A horizontal list of controls" 25 | slot footer 26 | 27 | def render(assigns) do 28 | ~F""" 29 |
30 | {#if slot_assigned?(:header)} 31 | <#slot {@header} /> 32 | {/if} 33 |
34 | <#slot {@image} /> 35 |
36 |
37 | <#slot /> 38 |
39 | 42 |
43 | """ 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/surface_bulma/card/footer-item.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Card.FooterItem do 2 | use Surface.Component, slot: "footer_items" 3 | 4 | @moduledoc """ 5 | Single to be displayed as a list in Card.Footer 6 | """ 7 | end 8 | -------------------------------------------------------------------------------- /lib/surface_bulma/card/footer.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Card.Footer do 2 | use Surface.Component 3 | 4 | @moduledoc """ 5 | The Card.Footer component acts as a list of for several Card.FooterItem elements 6 | 7 | ```elixir 8 | 9 | ... 10 | ... 11 | ... 12 | 13 | ``` 14 | """ 15 | 16 | @doc "Multi-purpose container for any other element" 17 | slot(footer_items, required: true) 18 | 19 | def render(assigns) do 20 | ~F""" 21 | 28 | """ 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/surface_bulma/card/header-icon.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Card.Header.Icon do 2 | use Surface.Component, slot: "icon" 3 | 4 | @moduledoc """ 5 | The header icon container. All actions defined by child contents. 6 | """ 7 | end 8 | -------------------------------------------------------------------------------- /lib/surface_bulma/card/header-title.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Card.Header.Title do 2 | use Surface.Component, slot: "title" 3 | 4 | @moduledoc """ 5 | The header title 6 | """ 7 | 8 | use SurfaceBulma.ClassProp 9 | slot default 10 | 11 | def render(assigns) do 12 | ~F""" 13 |

14 | <#slot /> 15 |

16 | """ 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/surface_bulma/card/header.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Card.Header do 2 | use Surface.Component, slot: "header" 3 | 4 | @moduledoc """ 5 | A horizontal bar with a shadow 6 | """ 7 | 8 | use SurfaceBulma.ClassProp 9 | 10 | @doc "A left-aligned bold text" 11 | slot title 12 | 13 | @doc "A container for an icon" 14 | slot icon 15 | 16 | def render(assigns) do 17 | ~F""" 18 |
19 | <#slot {@title} :if={slot_assigned?(:title)} /> 20 | 21 | 22 | <#slot {@icon} /> 23 | 24 |
25 | """ 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/surface_bulma/card/image.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Card.Image do 2 | use Surface.Component 3 | 4 | @moduledoc """ 5 | A fullwidth container for a responsive image 6 | """ 7 | 8 | @doc "Multi-purpose container for any other element" 9 | slot(default, required: true) 10 | 11 | def render(assigns) do 12 | ~F""" 13 |
14 |
15 | <#slot /> 16 |
17 |
18 | """ 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/surface_bulma/collapsible.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Collapsible do 2 | use SurfaceBulma.Component, propagate_context_to_slots: true 3 | alias Phoenix.LiveView.JS 4 | alias SurfaceBulma.Icon.FontAwesome, as: FA 5 | 6 | use SurfaceBulma.ClassProp 7 | prop id, :string, required: true 8 | prop header_class, :css_class 9 | prop collapsed, :boolean, default: true 10 | prop shadow, :boolean, default: false 11 | prop body_class, :css_class, default: [] 12 | prop remove, :event 13 | prop title, :string 14 | prop icon, :string 15 | prop toggle_icon, :any 16 | prop is_collapsed?, :boolean 17 | prop hook, :string 18 | 19 | slot default, required: true 20 | slot header 21 | slot header_icon 22 | slot footer 23 | 24 | def update(assigns, socket) do 25 | {:ok, 26 | assign(socket, assigns) 27 | |> assign( 28 | :is_collapsed?, 29 | is_nil(socket.assigns[:is_collapsed?]) || socket.assigns.is_collapsed? 30 | )} 31 | end 32 | 33 | def render(assigns) do 34 | ~F""" 35 |
36 |
37 |
38 | <#slot {@header} /> 39 |
40 |
41 |
JS.toggle(to: "##{@id} .angle-down") 43 | |> JS.toggle(to: "##{@id} .angle-up")}> 44 | 45 | 46 |
47 |
48 |
49 |
50 | <#slot /> 51 |
52 | 55 |
56 | """ 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/surface_bulma/component.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Component do 2 | defmacro __using__(_) do 3 | quote do 4 | use Surface.Component 5 | import SurfaceUtils 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/surface_bulma/delete.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Delete do 2 | @moduledoc """ 3 | The delete component is a stand-alone element that can be used in different contexts. On its own, 4 | it's a simple circle with a cross. 5 | """ 6 | 7 | use Surface.Component 8 | 9 | use SurfaceBulma.SizeProp 10 | 11 | @doc "Triggered on click" 12 | prop click, :event 13 | 14 | def render(assigns) do 15 | ~F""" 16 | 23 | 24 | """ 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/surface_bulma/divider.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Divider do 2 | use Surface.Component 3 | alias SurfaceBulma.{ClassProp, ContextClass} 4 | 5 | @moduledoc """ 6 | A divider that can be used in a `Dropdown` or a `Navbar`. It will adapt based on the context. 7 | """ 8 | use ClassProp 9 | use ContextClass 10 | 11 | def render(assigns) do 12 | ~F""" 13 |
14 | """ 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/surface_bulma/dropdown.ex: -------------------------------------------------------------------------------- 1 | defmodule SurfaceBulma.Dropdown do 2 | use Surface.LiveComponent 3 | 4 | @moduledoc """ 5 | An interactive dropdown menu for discoverable content. 6 | 7 | [Bulma Docs - Dropdown](https://bulma.io/documentation/components/dropdown/) 8 | """ 9 | 10 | @doc "Is always open" 11 | prop active, :boolean 12 | 13 | @doc "Activates on hover" 14 | prop hoverable, :boolean 15 | 16 | @doc "Left/Right aligned (left by default)" 17 | prop right_aligned, :boolean, default: false 18 | 19 | @doc "Menu appears above/below (below by default)" 20 | prop drop_up, :boolean, default: false 21 | 22 | slot trigger, required: true 23 | 24 | slot default, required: true 25 | 26 | data open, :boolean, default: false 27 | 28 | def render(assigns) do 29 | ~F""" 30 |
40 |