anchor_view_components 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 690f9aa0026033a9a42653fcebdd8cac19573199c1a738961d1e0e99637a211b
4
- data.tar.gz: 8c2287d823d21ddb99d766bcc8feda6204ae7807104cca805564c79692bf6efc
3
+ metadata.gz: fc1868d9abc000e67ae9aa10f38a2d462cdcbf2894f96f76ef9f400f899f633c
4
+ data.tar.gz: 9c43abbcfc0e5e746878c90c6379ad3ad5496c26d90fbb0f2723bf581d8c2404
5
5
  SHA512:
6
- metadata.gz: d35cd7a26d617a935c89d0817b231ee6f623e7abfab537cccec147d867b7710c6fc409d0b49c463458f25adbe3a829dd6912653f9d49bbaf60600e6707a4391d
7
- data.tar.gz: 137e7360312efa49b781297351a49d77b7e5d8b5e27117e9f5d5197fd375159f89bffb18ac48d220a5bc7934bec0a6755dfd47e185c569028aaec318c135e0b5
6
+ metadata.gz: 6f1201868d3abb571db947c49f8518a5bdee54b610a3c1282fe983e9deaa780ce24280c42fe514266e9640cc9135a6e8d1889aa99119a302e846bbc664ac24e1
7
+ data.tar.gz: 7cb9fdec805731bace8256159511575c9c36e525f26253253f5c849143784ea30636bf6921db368531db8e76a24c2be88f8c504d24001c06220cebe31a1e624b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.3 - 2023-08-28
4
+
5
+ ### Added
6
+
7
+ - Add TableComponent and helper
8
+ - Introduce `Invoker` Stimulus controller
9
+
3
10
  ## 0.9.2 - 2023-08-25
4
11
 
5
12
  ### Fixed
data/README.md CHANGED
@@ -48,7 +48,10 @@
48
48
 
49
49
  ## Development
50
50
 
51
- TODO
51
+ To see component previews:
52
+
53
+ - Run `yarn build:css --watch` in the root of the project
54
+ - Run `bin/dev` in the `demo/` directory
52
55
 
53
56
  ## License
54
57
 
@@ -0,0 +1,3 @@
1
+ <svg width="24px" height="24px" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3 5h18M3 12h18M3 19h18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
3
+ </svg>
@@ -3,14 +3,18 @@ import "@oddbird/popover-polyfill";
3
3
  import ActionMenuController from "./action_menu_controller";
4
4
  import AutocompleteController from "./autocomplete_controller";
5
5
  import DialogController from "./dialog_controller";
6
+ import InvokerController from "./invoker_controller";
6
7
  import ToastController from "./toast_controller";
7
8
  import ToggleController from "./toggle_controller";
9
+ import Sortable from "stimulus-sortable";
8
10
  import { Application } from "@hotwired/stimulus";
9
11
 
10
12
  export function registerAnchorControllers(application: Application) {
11
13
  application.register("action-menu", ActionMenuController);
12
14
  application.register("autocomplete", AutocompleteController);
13
15
  application.register("dialog", DialogController);
16
+ application.register("invoker", InvokerController);
17
+ application.register("sortable", Sortable)
14
18
  application.register("toast", ToastController);
15
19
  application.register("toggle", ToggleController);
16
20
  }
@@ -19,6 +23,7 @@ export {
19
23
  ActionMenuController,
20
24
  AutocompleteController,
21
25
  DialogController,
26
+ InvokerController,
22
27
  ToastController,
23
28
  ToggleController
24
29
  };
@@ -1,45 +1,43 @@
1
- <%= tag.div(
2
- class: "contents",
3
- data: { controller: "dialog" },
1
+ <%= show_button if show_button? %>
2
+
3
+ <%= tag.dialog(
4
+ aria: { labelledby: title_id },
5
+ class: "w-[36rem] rounded-lg p-0 bg-white shadow-lg backdrop:bg-grey-100/50",
6
+ data: {
7
+ controller: "dialog",
8
+ testid: title_id,
9
+ },
4
10
  id: id,
5
11
  ) do %>
6
- <%= show_button if show_button? %>
7
-
8
- <%= tag.dialog(
9
- aria: { labelledby: title_id },
10
- class: "w-[36rem] rounded-lg p-0 bg-white shadow-lg backdrop:bg-grey-100/50",
11
- data: { dialog_target: "dialog", testid: title_id },
12
- ) do %>
13
- <header class="p-6 flex gap-4 justify-between items-center">
14
- <%= render Anchor::TextComponent.new(
15
- variant: :heading_2xl,
16
- tag: :h1,
17
- id: title_id,
18
- data: { testid: "#{title_id}-title" }
19
- ).with_content(title) %>
12
+ <header class="p-6 flex gap-4 justify-between items-center">
13
+ <%= render Anchor::TextComponent.new(
14
+ variant: :heading_2xl,
15
+ tag: :h1,
16
+ id: title_id,
17
+ data: { testid: "#{title_id}-title" }
18
+ ).with_content(title) %>
20
19
 
21
- <form class="contents" method="dialog">
22
- <%= tag.button(
23
- aria: { label: "Close" },
24
- class: "text-grey-50 hover:text-grey-70",
25
- data: { action: "dialog#close" },
26
- ) do %>
27
- <%= render Anchor::IconComponent.new(icon: "cancel") %>
28
- <% end %>
29
- </form>
30
- </header>
20
+ <form class="contents" method="dialog">
21
+ <%= tag.button(
22
+ aria: { label: "Close" },
23
+ class: "text-grey-50 hover:text-grey-70",
24
+ data: { action: "dialog#close" },
25
+ ) do %>
26
+ <%= render Anchor::IconComponent.new(icon: "cancel") %>
27
+ <% end %>
28
+ </form>
29
+ </header>
31
30
 
32
- <div class="px-6 pb-6">
33
- <%= render(Anchor::TextComponent.new(
34
- variant: :body_base,
35
- data: { testid: "#{title_id}-body" }
36
- ).with_content(body)) %>
37
- </div>
31
+ <div class="px-6 pb-6">
32
+ <%= render(Anchor::TextComponent.new(
33
+ variant: :body_base,
34
+ data: { testid: "#{title_id}-body" }
35
+ ).with_content(body)) %>
36
+ </div>
38
37
 
39
- <% if footer? %>
40
- <footer class="sticky bottom-0 bg-white px-6 pb-6 flex gap-2 justify-end">
41
- <%= footer %>
42
- </footer>
43
- <% end %>
38
+ <% if footer? %>
39
+ <footer class="sticky bottom-0 bg-white px-6 pb-6 flex gap-2 justify-end">
40
+ <%= footer %>
41
+ </footer>
44
42
  <% end %>
45
43
  <% end %>
@@ -2,15 +2,20 @@ module Anchor
2
2
  class DialogComponent < Component
3
3
  renders_one :show_button, ->(**kwargs) do
4
4
  ButtonComponent.new(
5
- data: { action: "dialog#showModal",
6
- testid: "#{title.parameterize}-btn" },
5
+ data: {
6
+ action: "invoker#openDialog",
7
+ controller: "invoker",
8
+ invoker_dialog_outlet: "##{id}",
9
+ testid: "#{title.parameterize}-btn",
10
+ },
7
11
  **kwargs
8
12
  )
9
13
  end
10
14
  renders_one :body
11
15
  renders_one :footer
12
16
 
13
- def initialize(title:, **kwargs)
17
+ def initialize(id:, title:, **kwargs)
18
+ @id = id
14
19
  @title = title
15
20
 
16
21
  super
@@ -18,7 +23,7 @@ module Anchor
18
23
 
19
24
  private
20
25
 
21
- attr_reader :title
26
+ attr_reader :id, :title
22
27
 
23
28
  def render?
24
29
  body?
@@ -1,15 +1,11 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
2
 
3
- export default class extends Controller<HTMLDivElement> {
4
- static targets = ["dialog"];
5
-
6
- declare readonly dialogTarget: HTMLDialogElement;
7
-
3
+ export default class extends Controller<HTMLDialogElement> {
8
4
  showModal(): void {
9
- this.dialogTarget.showModal();
5
+ this.element.showModal();
10
6
  }
11
7
 
12
8
  close(): void {
13
- this.dialogTarget.close();
9
+ this.element.close();
14
10
  }
15
11
  }
@@ -0,0 +1,12 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import DialogController from "./dialog_controller";
3
+
4
+ export default class extends Controller {
5
+ static outlets = ["dialog"];
6
+
7
+ declare readonly dialogOutlet: DialogController;
8
+
9
+ openDialog(): void {
10
+ this.dialogOutlet.showModal();
11
+ }
12
+ }
@@ -0,0 +1,37 @@
1
+ <table class="w-full text-base text-left">
2
+ <thead>
3
+ <tr class="border-b">
4
+ <% columns.each do |column| %>
5
+ <%= tag.th(
6
+ column.header,
7
+ class: "px-2 py-3 font-semibold",
8
+ scope: "col",
9
+ ) %>
10
+ <% end %>
11
+ </tr>
12
+ </thead>
13
+
14
+ <%= tag.tbody(data: tbody_data) do
15
+ data.each do |model| %>
16
+ <%= tag.tr(
17
+ class: row_classes,
18
+ data: {
19
+ sortable_update_url: sort_url(model),
20
+ testid: "tr-#{model.id}"
21
+ },
22
+ ) do
23
+ columns.each.with_index do |column, index| %>
24
+ <%= tag.td(
25
+ maybe_link(model, column, index, self).to_s.html_safe,
26
+ class: class_names(
27
+ "relative",
28
+ "p-3 pl-8 bg-[url('assets/icons/menu.svg')] bg-[length:16px_16px] bg-[8px_center] bg-no-repeat": sortable? && index.zero?,
29
+ "px-2 py-3": !sortable? || index.positive?,
30
+ ),
31
+ data: { testid: "tr-#{model.id}-td-#{index}" },
32
+ ) %>
33
+ <% end
34
+ end
35
+ end
36
+ end %>
37
+ </table>
@@ -0,0 +1,90 @@
1
+ module Anchor
2
+ class TableComponent < Component
3
+ renders_many :columns, "ColumnComponent"
4
+ renders_one :link
5
+
6
+ attr_reader :data, :sortable
7
+
8
+ def initialize(data:, sortable: false, sort_url: nil)
9
+ @data = data
10
+ @sortable = sortable
11
+ @sort_url = sort_url
12
+
13
+ super
14
+ end
15
+
16
+ private
17
+
18
+ def row_classes
19
+ row_classes = %w(border-b focus-within:bg-grey-10)
20
+ if link?
21
+ row_classes += %w(hover:bg-grey-10 hover:cursor-pointer)
22
+ end
23
+ if sortable?
24
+ row_classes += %w(hover:cursor-pointer)
25
+ end
26
+ row_classes.join(" ")
27
+ end
28
+
29
+ def maybe_link(model, column, index, view_context)
30
+ value = value_for(model, column)
31
+ if link?
32
+ path = "#{link}#{model.id}"
33
+ if index.zero?
34
+ text = view_context.link_to(value, path)
35
+ else
36
+ text = value
37
+ end
38
+ text + view_context.link_to(
39
+ "",
40
+ path,
41
+ aria: { hidden: true },
42
+ class: "absolute inset-0",
43
+ tabindex: "-1"
44
+ )
45
+ else
46
+ value
47
+ end
48
+ end
49
+
50
+ def value_for(model, column)
51
+ if column.value.respond_to?(:call)
52
+ column.value.call(model)
53
+ else
54
+ model[column.value]
55
+ end
56
+ end
57
+
58
+ def tbody_data
59
+ if sortable?
60
+ { controller: "sortable", testid: "sortable" }
61
+ else
62
+ {}
63
+ end
64
+ end
65
+
66
+ def sort_url(model)
67
+ if sortable?
68
+ @sort_url.call(model)
69
+ end
70
+ end
71
+
72
+ def sortable?
73
+ sortable
74
+ end
75
+
76
+ def render?
77
+ columns.present? && data.present?
78
+ end
79
+
80
+ class ColumnComponent < Anchor::Component
81
+ attr_reader :header, :value
82
+
83
+ def initialize(header:, value:)
84
+ @header = header
85
+ @value = value
86
+ super
87
+ end
88
+ end
89
+ end
90
+ end
@@ -14,6 +14,7 @@ module Anchor
14
14
  prose
15
15
  side_nav
16
16
  tab_bar
17
+ table
17
18
  toast
18
19
  ].freeze
19
20
 
@@ -1,5 +1,5 @@
1
1
  module Anchor
2
2
  module ViewComponents
3
- VERSION = "0.9.2".freeze
3
+ VERSION = "0.9.3".freeze
4
4
  end
5
5
  end
@@ -0,0 +1,23 @@
1
+ <%= render Anchor::ActionMenuComponent.new do |c| %>
2
+ <% c.with_show_button_content("Menu") %>
3
+
4
+ <% c.with_item do %>
5
+ <%= tag.button(
6
+ "Show dialog",
7
+ data: {
8
+ action: "invoker#openDialog",
9
+ controller: "invoker",
10
+ invoker_dialog_outlet: "#my-dialog",
11
+ },
12
+ ) %>
13
+ <% end %>
14
+ <% end %>
15
+
16
+ <%= render Anchor::DialogComponent.new(
17
+ id: "my-dialog",
18
+ title: "Dialog Title",
19
+ ) do |dialog| %>
20
+ <% dialog.with_body do %>
21
+ <%= tag.p "Content" %>
22
+ <% end %>
23
+ <% end %>
@@ -18,5 +18,7 @@ module Anchor
18
18
  end
19
19
  end
20
20
  end
21
+
22
+ def opens_a_dialog; end
21
23
  end
22
24
  end
@@ -0,0 +1,16 @@
1
+ <%= render Anchor::ButtonComponent.new(
2
+ data: {
3
+ action: "invoker#openDialog",
4
+ controller: "invoker",
5
+ invoker_dialog_outlet: "#my-dialog",
6
+ },
7
+ ).with_content("Show dialog") %>
8
+
9
+ <%= render Anchor::DialogComponent.new(
10
+ id: "my-dialog",
11
+ title: "Dialog Title",
12
+ ) do |dialog| %>
13
+ <% dialog.with_body do %>
14
+ <%= tag.p "Content" %>
15
+ <% end %>
16
+ <% end %>
@@ -1,4 +1,5 @@
1
1
  <%= render Anchor::DialogComponent.new(
2
+ id: "my-dialog",
2
3
  title: "Dialog Title",
3
4
  ) do |dialog| %>
4
5
  <% dialog.with_show_button.with_content("Show Dialog") %>
@@ -16,8 +16,16 @@ module Anchor
16
16
 
17
17
  def with_footer; end
18
18
 
19
+ # The Dialog component has a `show_button` slot, but it’s optional and you
20
+ # can instead provide your own button to open the Dialog, as shown in this
21
+ # example.
22
+ def with_custom_show_button; end
23
+
19
24
  def with_link
20
- render Anchor::DialogComponent.new(title: "Dialog Title") do |c|
25
+ render Anchor::DialogComponent.new(
26
+ id: "my-dialog",
27
+ title: "Dialog Title"
28
+ ) do |c|
21
29
  c.with_show_button(classes: "all-unset underline")
22
30
  .with_content("Show Dialog")
23
31
  c.with_body.with_content("Content")
@@ -0,0 +1,48 @@
1
+ module Anchor
2
+ class TableComponentPreview < ViewComponent::Preview
3
+ class MockData
4
+ attr_reader :id, :name
5
+
6
+ def initialize(id, name)
7
+ @id = id
8
+ @name = name
9
+ end
10
+
11
+ def [](key)
12
+ public_send(key)
13
+ end
14
+ end
15
+
16
+ def playground
17
+ render Anchor::TableComponent.new(data:) do |table|
18
+ table.with_column(header: "Id", value: :id)
19
+ table.with_column(header: "Name", value: -> { _1.name.capitalize })
20
+ end
21
+ end
22
+
23
+ def with_link
24
+ render Anchor::TableComponent.new(data:) do |table|
25
+ table.with_link { "#a_link/" }
26
+ table.with_column(header: "Id", value: :id)
27
+ table.with_column(header: "Name", value: -> { _1.name.capitalize })
28
+ end
29
+ end
30
+
31
+ def sortable
32
+ render Anchor::TableComponent.new(
33
+ data:,
34
+ sortable: true,
35
+ sort_url: ->(data) { "/sort/#{data.id}" }
36
+ ) do |table|
37
+ table.with_column(header: "Id", value: :id)
38
+ table.with_column(header: "Name", value: -> { _1.name.capitalize })
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def data
45
+ (1..4).map { MockData.new(_1, "name #{_1}") }
46
+ end
47
+ end
48
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anchor_view_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Buoy Software
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-25 00:00:00.000000000 Z
11
+ date: 2023-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -51,6 +51,7 @@ files:
51
51
  - app/assets/images/icons/actions-check-circle-base.svg
52
52
  - app/assets/images/icons/cancel.svg
53
53
  - app/assets/images/icons/fast-left-circle.svg
54
+ - app/assets/images/icons/menu.svg
54
55
  - app/assets/images/icons/nav-arrow-down.svg
55
56
  - app/assets/images/icons/nav-arrow-left.svg
56
57
  - app/assets/images/icons/nav-arrow-right.svg
@@ -82,6 +83,7 @@ files:
82
83
  - app/components/anchor/dialog_controller.ts
83
84
  - app/components/anchor/icon_component.html.erb
84
85
  - app/components/anchor/icon_component.rb
86
+ - app/components/anchor/invoker_controller.ts
85
87
  - app/components/anchor/loading_indicator_component.html.erb
86
88
  - app/components/anchor/loading_indicator_component.rb
87
89
  - app/components/anchor/panel/body_component.html.erb
@@ -101,6 +103,8 @@ files:
101
103
  - app/components/anchor/tab_bar/tab_component.rb
102
104
  - app/components/anchor/tab_bar_component.html.erb
103
105
  - app/components/anchor/tab_bar_component.rb
106
+ - app/components/anchor/table_component.html.erb
107
+ - app/components/anchor/table_component.rb
104
108
  - app/components/anchor/text_component.html.erb
105
109
  - app/components/anchor/text_component.rb
106
110
  - app/components/anchor/toast_component.html.erb
@@ -115,11 +119,13 @@ files:
115
119
  - lib/anchor/view_components/version.rb
116
120
  - lib/anchor_view_components.rb
117
121
  - previews/anchor/action_menu_component_preview.rb
122
+ - previews/anchor/action_menu_component_preview/opens_a_dialog.html.erb
118
123
  - previews/anchor/badge_component_preview.rb
119
124
  - previews/anchor/banner_component_preview.rb
120
125
  - previews/anchor/breadcrumbs_component_preview.rb
121
126
  - previews/anchor/button_component_preview.rb
122
127
  - previews/anchor/dialog_component_preview.rb
128
+ - previews/anchor/dialog_component_preview/with_custom_show_button.html.erb
123
129
  - previews/anchor/dialog_component_preview/with_footer.html.erb
124
130
  - previews/anchor/icon_component_preview.rb
125
131
  - previews/anchor/loading_indicator_component_preview.rb
@@ -128,6 +134,7 @@ files:
128
134
  - previews/anchor/side_nav_component_preview.rb
129
135
  - previews/anchor/side_nav_component_preview/default.html.erb
130
136
  - previews/anchor/tab_bar_component_preview.rb
137
+ - previews/anchor/table_component_preview.rb
131
138
  - previews/anchor/text_component_preview.rb
132
139
  - previews/anchor/toast_component_preview.rb
133
140
  homepage: https://github.com/BuoySoftware/anchor_view_components