x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<div class="p-8"><turbo-frame id="preview-clickable-table"> <div data-controller="midwest-table" class="midwest-table-wrapper"> <div class="midwest-table-loading-overlay" aria-hidden="true"> <div class="midwest-loading-icon" role="img" aria-label="Loading" decorative="true"> <div class="midwest-loading-icon-inner"> <!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL --> <svg viewbox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="Midwest-LoadingIcon__Svg" focusable="false" aria-hidden="true"> <circle cx="15" cy="15" r="15"> <animate attributename="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcmode="linear" repeatcount="indefinite"></animate> <animate attributename="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcmode="linear" repeatcount="indefinite"></animate> </circle> <circle cx="60" cy="15" r="9" fill-opacity="0.3"> <animate attributename="r" from="9" to="9" begin="0s" dur="0.8s" values="9;15;9" calcmode="linear" repeatcount="indefinite"></animate> <animate attributename="fill-opacity" from="0.5" to="0.5" begin="0s" dur="0.8s" values=".5;1;.5" calcmode="linear" repeatcount="indefinite"></animate> </circle> <circle cx="105" cy="15" r="15"> <animate attributename="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcmode="linear" repeatcount="indefinite"></animate> <animate attributename="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcmode="linear" repeatcount="indefinite"></animate> </circle> </svg> </div> </div> </div> <div class="midwest-table-scroll"> <table class="midwest-table"> <caption class="sr-only">Users</caption> <thead class="midwest-table-head"> <tr> <th scope="col" class="midwest-table-th align-start sortable sorted" aria-sort="ascending"> <a class="midwest-table-sort-link" href="?sort=name&dir=desc"> Name <span class="midwest-table-sort-icon" aria-hidden="true"></span> </a> </th> <th scope="col" class="midwest-table-th align-start sortable" aria-sort="none"> <a class="midwest-table-sort-link" href="?sort=email&dir=asc"> Email <span class="midwest-table-sort-icon" aria-hidden="true"></span> </a> </th> <th scope="col" class="midwest-table-th align-start sortable" aria-sort="none"> <a class="midwest-table-sort-link" href="?sort=role&dir=asc"> Role <span class="midwest-table-sort-icon" aria-hidden="true"></span> </a> </th> <th scope="col" class="midwest-table-th align-start"> Status </th> <th scope="col" class="midwest-table-th align-start"> Joined </th> </tr> </thead> <tbody class="midwest-table-body"> <tr class="midwest-table-row" data-href="#user-1" data-action="click->midwest-table#navigateRow"> <td class="midwest-table-td align-start"> Alice Johnson </td> <td class="midwest-table-td align-start"> alice@example.com </td> <td class="midwest-table-td align-start"> Engineer </td> <td class="midwest-table-td align-start"> <span variant="success" class="midwest-badge size-default theme-primary"> <span class="value">Active</span> </span> </td> <td class="midwest-table-td align-start"> Jan 2022 </td> </tr> <tr class="midwest-table-row" data-href="#user-2" data-action="click->midwest-table#navigateRow"> <td class="midwest-table-td align-start"> Bob Smith </td> <td class="midwest-table-td align-start"> bob@example.com </td> <td class="midwest-table-td align-start"> Designer </td> <td class="midwest-table-td align-start"> <span variant="success" class="midwest-badge size-default theme-primary"> <span class="value">Active</span> </span> </td> <td class="midwest-table-td align-start"> Mar 2021 </td> </tr> <tr class="midwest-table-row" data-href="#user-3" data-action="click->midwest-table#navigateRow"> <td class="midwest-table-td align-start"> Carol White </td> <td class="midwest-table-td align-start"> carol@example.com </td> <td class="midwest-table-td align-start"> Manager </td> <td class="midwest-table-td align-start"> <span variant="neutral" class="midwest-badge size-default theme-primary"> <span class="value">Inactive</span> </span> </td> <td class="midwest-table-td align-start"> Nov 2020 </td> </tr> <tr class="midwest-table-row" data-href="#user-4" data-action="click->midwest-table#navigateRow"> <td class="midwest-table-td align-start"> David Lee </td> <td class="midwest-table-td align-start"> david@example.com </td> <td class="midwest-table-td align-start"> Engineer </td> <td class="midwest-table-td align-start"> <span variant="success" class="midwest-badge size-default theme-primary"> <span class="value">Active</span> </span> </td> <td class="midwest-table-td align-start"> Jul 2023 </td> </tr> <tr class="midwest-table-row" data-href="#user-5" data-action="click->midwest-table#navigateRow"> <td class="midwest-table-td align-start"> Eve Martinez </td> <td class="midwest-table-td align-start"> eve@example.com </td> <td class="midwest-table-td align-start"> Designer </td> <td class="midwest-table-td align-start"> <span variant="neutral" class="midwest-badge size-default theme-primary"> <span class="value">Inactive</span> </span> </td> <td class="midwest-table-td align-start"> Feb 2024 </td> </tr> </tbody> </table> </div> </div></turbo-frame></div>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<% row_class = Struct.new(:id, :name, :email, :role, :active, :created_at, keyword_init: true) # Inline presenter subclass for the preview (normally lives in app/tables/) preview_table_class = Class.new(Midwest::TablePresenter) do default_sort :name default_direction :asc allowed_sorts %i[name email role] # Full-row click: each row navigates to the URL returned by the callable. # In a real app you would use view helpers: # row_href ->(row, view) { view.user_path(row) } row_href ->(row) { "#user-#{row.id}" } column 'Name', key: :name, sortable: true column 'Email', key: :email, sortable: true column 'Role', key: :role, sortable: true column 'Status' do |row, view| variant = row.active ? :success : :neutral view.midwest_badge(row.active ? 'Active' : 'Inactive', variant: variant) end column 'Joined', key: :created_at def derived_frame_id 'preview-clickable-table' end end rows = [ row_class.new(id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Engineer', active: true, created_at: 'Jan 2022'), row_class.new(id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'Designer', active: true, created_at: 'Mar 2021'), row_class.new(id: 3, name: 'Carol White', email: 'carol@example.com', role: 'Manager', active: false, created_at: 'Nov 2020'), row_class.new(id: 4, name: 'David Lee', email: 'david@example.com', role: 'Engineer', active: true, created_at: 'Jul 2023'), row_class.new(id: 5, name: 'Eve Martinez', email: 'eve@example.com', role: 'Designer', active: false, created_at: 'Feb 2024'), ] table = preview_table_class.new( params: ActionController::Parameters.new('sort' => 'name', 'dir' => 'asc'), scope: rows, request: nil )%><div class="p-8"> <%= turbo_frame_tag table.frame_id do %> <%= midwest_table_from table, caption: 'Users' %> <% end %></div>Clickable Rows With Presenter
Use the row_href DSL in a Midwest::TablePresenter subclass. The
callable receives the row (and optionally the view context for URL helpers)
and midwest_table_from wires it automatically.
No params configured.
No assets to display.
Add a .js or .css file alongside the component to see it here.
Figma embed for Table/clickable_rows_with_presenter
Annotate a preview to embed its Figma design here:
@figma "https://figma.com/..."