protos 0.4.3 → 0.6.0

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: d7b9c6eb83f7c89f66d1f625fd200b2f6595c098130e16da1fcb6b2e96dd59fe
4
- data.tar.gz: 07a7e91d1ef7041dba38b72eb412309d31cf6426ad1e3b157f6a83fab59a32dd
3
+ metadata.gz: '0796561a80e75fb39f8b35ec75257104adc90b21227ef1525876576d10415269'
4
+ data.tar.gz: 394a8ee190d1d2647476db45d2d9b9edb156bc42a3935898d6f1deca35261326
5
5
  SHA512:
6
- metadata.gz: 36fa9e7809d68b3d1227814cb7bb23fc44066813de58360486f5c7ffffafb30f58f6cb50309aef8902796ea234dfe3f5dcd46b451673cf77600da5f257840e33
7
- data.tar.gz: 8ee74d55eebfaaec0d488809563695bf456229ed8dad466a10b6b5f20c117ed02644ef2a7a9646679a47697cae06eca5e6e50f21a863292d46c9753be4c82939
6
+ metadata.gz: 59e2288d2ae8adfc3b352838dda25c776edddf48d99b310181629e78f09831552be23e36adedaca7bf3601678cb08950500f4b72f2ed609324ccd72ddd32a92d
7
+ data.tar.gz: e0e4beda7e4cbff4e3b669899b65df2ec7c37fe388e75b561418ce1cc65abd4cb1cfecabd743f964bf0dee98de1c4ccdbf9f7e76f77078f294a8bfb0a4272802
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.0] - 2024-09-04
4
+
5
+ - Changes how merging attributes works to only mix on certain attributes,
6
+ overriding on all others. This is opposite to how attributes used to be merged
7
+ by default. This is a fix for attributes like `value` where you actually need
8
+ to override them.
9
+ - Adds tests for all Rails components
10
+ - Adds a separate tested `Mix` class for handling attribute merging
11
+
12
+ ## [0.5.0] - 2024-08-27
13
+
14
+ - Fixes all accessibility violations according to Axe Core
15
+ - Reduces responsibility of Tabs to only be a tablist, no tab panels
16
+ - Fixes passing ID to popovers, dropdowns, drawers, etc to not override the
17
+ input ID
18
+ - Changes trigger on popover to be a button instead of a div
19
+
3
20
  ## [0.4.3] - 2024-08-14
4
21
 
5
22
  - Removes unneeded auto-loading in Rails which fixes collisions with `protos-markdown`
data/README.md CHANGED
@@ -20,10 +20,90 @@ Other Phlex based UI libraries worth checking out:
20
20
  - [PhlexUI](https://phlexui.com/)
21
21
  - [ZestUI](https://zestui.com/)
22
22
 
23
+ Thinking of making your next static site using Phlex? Check out
24
+ [staticky](https://github.com/nolantait/staticky). The protos docs were
25
+ published using it.
26
+
27
+ ## Phlex components
28
+
29
+ Phlex is a fantastic framework for building frontend components in pure Ruby:
30
+
31
+ ```ruby
32
+ class Navbar
33
+ def view_template
34
+ header(class: "flex items-center justify-between") do
35
+ h3 { "My site" }
36
+ button { "Log out" }
37
+ end
38
+ end
39
+ end
40
+ ```
41
+
42
+ But how can we sometimes render this `Navbar` with a different background color?
43
+
44
+ It would be nice to have our components take a class like any other element:
45
+
46
+ ```ruby
47
+ render Navbar.new(class: "bg-primary")
48
+ ```
49
+
50
+ Unfortunately `class` is a special keyword in Ruby, so we need to do some
51
+ awkward handling to use it like this:
52
+
53
+ ```ruby
54
+ class Navbar
55
+ def initialize(**options)
56
+ # Keyword `class` is a special word in Ruby so we have to awkwardly unwrap
57
+ # like this instead of using keyword arguments
58
+ @classes = options[:class]
59
+ end
60
+
61
+ def view_template
62
+ header(class: "#{@classes} flex items-center justify-between") do
63
+ h3 { "My site" }
64
+ button { "Log out" }
65
+ end
66
+ end
67
+ end
68
+ ```
69
+
70
+ Now we can pass in a style to our container, but what about overriding the style
71
+ of the `h3` tag?
72
+
73
+ ```ruby
74
+ class Navbar
75
+ def initialize(**options)
76
+ # Keyword `class` is a special word in Ruby so we have to awkwardly unwrap
77
+ # like this instead of using keyword arguments
78
+ @container_classes = options[:class]
79
+ @title_classes = options[:title_class]
80
+ end
81
+
82
+ def view_template
83
+ header(class: "#{@classes} flex items-center justify-between") do
84
+ h3(class: @title_classes) { "My site" }
85
+ button { "Log out" }
86
+ end
87
+ end
88
+ end
89
+ ```
90
+
91
+ Eventually everyone makes a kind of ad-hoc system for specifying styles. It gets
92
+ more complicated when you have attributes like a data-controller. How do you
93
+ give a good experience letting people using your components to add their own
94
+ controllers while your component depends on one already?
95
+
96
+ This library is an attempt to make this kind of developer experience while
97
+ making reusable components more convention over configuration.
98
+
23
99
  ## Protos::Component
24
100
 
25
- A protos component follows some conventions that make them easy to work with as
26
- components in your app.
101
+ A protos component follows 3 conventions that make them easy to work with as
102
+ components in your app:
103
+
104
+ - [Slots and themes](#slots-and-themes)
105
+ - [Attrs and default attrs](#attrs-and-default-attrs)
106
+ - [Params and options](#params-and-options)
27
107
 
28
108
  Every UI component library will have a tension between being too general to fit
29
109
  in your app or too narrow to be useful. Making components that look good out of
@@ -32,20 +112,18 @@ the box can make them hard to customize.
32
112
  We try and resolve this tension by making these components have a minimal style
33
113
  that can be easily overridden using some ergonomic conventions.
34
114
 
35
- There are 3 core conventions:
36
- - [Slots and themes](#slots-and-themes)
37
- - [Attrs and default attrs](#attrs-and-default-attrs)
38
- - [Params and options](#params-and-options)
39
-
40
115
  ### Slots and themes
41
116
 
42
- Components are styled with `css` slots that are filled with values from
43
- a `theme`.
117
+ Components are styled with `css` slots that get their values from a simple hash
118
+ we call a `theme`.
119
+
120
+ You define a `theme` for your component by defining a `#theme` method that
121
+ returns a hash.
122
+
123
+ Users of your components can override, merge, or remove parts of your theme by
124
+ passing in their own as an argument to the component. Another nice benefit is
125
+ that your markup doesn't get overwhelmed horizontally with your css classes.
44
126
 
45
- You define a theme for your component by defining a `#theme` method that returns
46
- a hash. This hash will be merged with any theme provided when rendering your
47
- component. This allows you to easily override styles for your components
48
- depending on their context.
49
127
 
50
128
  ```ruby
51
129
  class List < Protos::Component
@@ -58,7 +136,7 @@ class List < Protos::Component
58
136
 
59
137
  def theme
60
138
  {
61
- list: tokens("space-y-4"), # We can use `#tokens` from phlex (recommended)
139
+ list: "space-y-4", # We can use `#tokens` from phlex (recommended)
62
140
  item: "font-bold text-2xl" # Or just plain old strings
63
141
  }
64
142
  end
@@ -66,7 +144,10 @@ end
66
144
  ```
67
145
 
68
146
  Using a theme and css slots allows us to easily override any part of a component
69
- when we render:
147
+ when we render.
148
+
149
+ Here we are passing in our own theme. The default behavior is to add these
150
+ styles on to the theme, rather than replacing them.
70
151
 
71
152
  ```ruby
72
153
  render List.new(
@@ -77,7 +158,12 @@ render List.new(
77
158
  )
78
159
  ```
79
160
 
80
- This would combine the default and our theme using tailwind\_merge:
161
+ When the component is rendered the `tailwind_merge` gem will also prune any
162
+ duplicate unneeded styles.
163
+
164
+ For example even though the themes `list` key would be added together to become
165
+ `space-y-4 space-y-8`, the `tailwind_merge` gem will prune it down to just
166
+ `space-y-8` as the two styles conflict.
81
167
 
82
168
  ```html
83
169
  <ul class="space-y-8">
@@ -119,8 +205,8 @@ The new `css[:item]` slot would be:
119
205
  <li class="font-bold">Item 1</li>
120
206
  ```
121
207
 
122
- If you want to change the method we define our default theme you can override the
123
- `theme_method` on the class:
208
+ If you want to change the method we define our default theme under you can
209
+ override the `theme_method` on the class:
124
210
 
125
211
  ```ruby
126
212
  class List < Protos::Component
@@ -142,12 +228,15 @@ end
142
228
  ### Attrs and default attrs
143
229
 
144
230
  By convention, all components spread in an `attrs` hash on their outermost
145
- element of the component.
231
+ element of the component. There is no rule for this, but it makes them feel more
232
+ naturally like native html elements when you render them.
146
233
 
147
- By doing this we enable 2 main conveniences:
234
+ By doing this we enable 3 main conveniences:
148
235
  1. We can pass a `class` keyword when initializing the component which will be
149
236
  merged safely into the `css[:container]` slot
150
- 2. We can add default attributes that are safely merged with any provided to the
237
+ 2. We can pass any html attributes we want to the element such as `id`, `data`
238
+ etc and it will just work
239
+ 3. We can add default attributes that are safely merged with any provided to the
151
240
  component when its being initialized
152
241
 
153
242
  ```ruby
@@ -175,12 +264,15 @@ class List < Protos::Component
175
264
  end
176
265
  ```
177
266
 
178
- `#attrs` will by default merge the `class` keyword into the `css[:container]`
179
- slot which we define in our theme.
267
+ `#attrs` returns a hash which will by default merge the `class` keyword into the
268
+ `css[:container]` slot which we define in our theme. The `ul` elements class
269
+ would be `space-y-4` as that is the `css[:container]` on our theme.
270
+
271
+ Special html options (`class`, `data`) will be safely merged.
180
272
 
181
- Special html options will be safely merged. For examples, the component above
182
- defines a list controller. If we passed our own controller into data when we
183
- initialize, the component's data-controller attribute would be appended to:
273
+ For examples, the component above defines a list controller. If we passed our
274
+ own controller into data when we initialize, the component's data-controller
275
+ attribute would be appended to:
184
276
 
185
277
  ```ruby
186
278
  render List.new(
@@ -194,6 +286,9 @@ That would output both controllers to the DOM element:
194
286
  <ul data-controller="list tooltip">
195
287
  ```
196
288
 
289
+ This makes it very convenient to add functionality to basic components without
290
+ overriding their core behavior or having to modify/override their class.
291
+
197
292
  If we wanted to, just like for our theme we can change the method from
198
293
  `default_attrs` by defining the `default_attrs_method` on the class:
199
294
 
@@ -224,25 +319,27 @@ class List < Protos::Component
224
319
  end
225
320
  ```
226
321
 
322
+ This makes our initialization declarative and easy to extend without having to
323
+ consider how to call `super` in the initializer.
324
+
227
325
  The following keywords are reserved in the base class:
228
326
 
229
327
  - `class`
230
328
  - `theme`
231
329
  - `html_options`
232
330
 
331
+ You are free to add whatever positional or keyword arguments you like as long as
332
+ they don't directly conflict with those names.
333
+
233
334
  ## Putting it all together
234
335
 
235
- Here is an example of a small navbar component:
336
+ Lets revisit the example of our `Navbar` component:
236
337
 
237
338
  ```ruby
238
339
  require "protos"
239
340
 
240
341
  class Navbar < Protos::Component
241
342
  def view_template
242
- # **attrs will add:
243
- # - Any html options defined on the component initialization such as data,
244
- # role, for, etc..
245
- # - Class will be added to the css[:container] and applied
246
343
  header(**attrs) do
247
344
  h1(class: css[:heading]) { "Hello world" }
248
345
  h2(class: css[:subtitle]) { "With a subtitle" }
@@ -259,19 +356,19 @@ class Navbar < Protos::Component
259
356
 
260
357
  def theme
261
358
  {
262
- container: tokens(
263
- "flex",
264
- "justify-between",
265
- "items-center",
266
- "gap-sm"
267
- ),
268
- heading: tokens("text-2xl", "font-bold"),
269
- subtitle: tokens("text-base")
359
+ container: "flex justify-between items-center gap-sm",
360
+ heading: "text-2xl font-bold",
361
+ subtitle: "text-sm"
270
362
  }
271
363
  end
272
364
  end
365
+ ```
273
366
 
274
- component = Navbar.new(
367
+ Now all the concerns about adding in our behavior, styles, etc are handled for
368
+ us by convention:
369
+
370
+ ```ruby
371
+ render Navbar.new(
275
372
  # This will add to the component's css[:container] slot
276
373
  class: "my-sm",
277
374
  # This will add the controller and not remove
@@ -283,8 +380,6 @@ component = Navbar.new(
283
380
  subtitle!: "text-xl" # We can override the entire slot
284
381
  }
285
382
  )
286
-
287
- puts component.call
288
383
  ```
289
384
 
290
385
  Which produces the following html:
@@ -308,7 +403,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
308
403
 
309
404
  ## Usage
310
405
 
311
- Setup [Tailwindcss](https://tailwindcss.com/), [daisyUI](https://daisyui.com)
406
+ Setup [TailwindCSS](https://tailwindcss.com/), [DaisyUI](https://daisyui.com)
312
407
  and add the protos path to your content.
313
408
 
314
409
  ```
@@ -316,7 +411,7 @@ npm install -D tailwindcss postcss autoprefixer daisyui
316
411
  npx tailwindcss init
317
412
  ```
318
413
 
319
- Then we need to add the protos path to the `content` of our tailwindcss config
414
+ Then we need to add the protos path to the `content` of our tailwind config
320
415
  so tailwind will read the styles defined in the Protos gem.
321
416
 
322
417
  Protos also uses semantic spacing such as `p-sm` or `m-md` instead of set
@@ -344,15 +439,6 @@ module.exports = {
344
439
  lg: "var(--spacing-lg)",
345
440
  xl: "var(--spacing-xl)",
346
441
  },
347
- // If you use % based spacing you might want different spacing
348
- // for any vertical gaps to prevent overflow
349
- gap: {
350
- xs: "var(--spacing-gap-xs)",
351
- sm: "var(--spacing-gap-sm)",
352
- md: "var(--spacing-gap-md)",
353
- lg: "var(--spacing-gap-lg)",
354
- xl: "var(--spacing-gap-xl)",
355
- },
356
442
  },
357
443
  }
358
444
  // ....
@@ -388,11 +474,12 @@ end
388
474
 
389
475
  ## Building your own components
390
476
 
391
- You can override components simply by redefining the class in your own app:
477
+ You can override components simply by redefining sub-classing the class in your
478
+ own app:
392
479
 
393
480
  ```ruby
394
- module Protos
395
- class Swap < Component
481
+ module Components
482
+ class Swap < Protos::Component
396
483
  private
397
484
 
398
485
  def on(...)
@@ -408,8 +495,22 @@ module Protos
408
495
  end
409
496
  ```
410
497
 
411
- You could also encapsulate these more primitive proto components into your own
412
- components. You could use `Proto::List` to create your own list and even use
498
+ But its much better to avoid the sub-classing and just render the component
499
+ inside of your own:
500
+
501
+ ```ruby
502
+ module Components
503
+ class Swap < ApplicationComponent
504
+ def view_template
505
+ render Protos::Swap.new do |c|
506
+ # ....
507
+ end
508
+ end
509
+ end
510
+ end
511
+ ```
512
+
513
+ You could use `Proto::List` to create your own list and even use
413
514
  `Phlex::DeferredRender` to make the API more convenient.
414
515
 
415
516
  Let's create a list component with headers and actions:
@@ -5,16 +5,16 @@ module Protos
5
5
  class Item < Component
6
6
  # DOCS: An accorion is just a collapse with radio buttons.
7
7
 
8
- option :id, type: Types::Coercible::String
8
+ option :input_id, type: Types::String | Types::Integer
9
9
 
10
10
  def view_template(&block)
11
11
  li(**attrs) do
12
- render Collapse.new(theme: collapse_theme) do
13
- # form: "" prevents the radio button from being submitted if its
14
- # within a form
15
- input(type: :radio, name: id, form: "", autocomplete: :off)
16
- yield if block
17
- end
12
+ render Collapse.new(
13
+ theme: collapse_theme,
14
+ input_id: @input_id.to_s,
15
+ input_type: :radio,
16
+ &block
17
+ )
18
18
  end
19
19
  end
20
20
 
@@ -7,20 +7,30 @@ module Protos
7
7
  # to be open at the same time, use the collapse component.
8
8
  # https://daisyui.com/components/accordion/
9
9
 
10
- option :id, default: -> { "collapse-#{SecureRandom.hex(4)}" }
11
-
12
10
  def view_template(&)
13
11
  ul(**attrs, &)
14
12
  end
15
13
 
16
- def item(*, **, &) = render Item.new(*, id:, **, &)
14
+ def item(*, input_id: nil, **, &)
15
+ self.current_input_id = input_id
16
+
17
+ render Item.new(*, input_id: current_input_id, **, &)
18
+ end
17
19
 
18
20
  def content(...) = render Collapse::Content.new(...)
19
21
 
20
- def title(*, **, &) = render Collapse::Title.new(*, id:, **, &)
22
+ def title(*, **, &)
23
+ render Collapse::Title.new(*, input_id: current_input_id, **, &)
24
+ end
21
25
 
22
26
  private
23
27
 
28
+ attr_reader :current_input_id
29
+
30
+ def current_input_id=(value)
31
+ @current_input_id = value || "collapse-#{SecureRandom.hex(4)}"
32
+ end
33
+
24
34
  def theme
25
35
  {
26
36
  container: "join join-vertical"
@@ -11,6 +11,12 @@ module Protos
11
11
 
12
12
  private
13
13
 
14
+ def default_attrs
15
+ {
16
+ aria_label: "alert-actions"
17
+ }
18
+ end
19
+
14
20
  def theme
15
21
  {
16
22
  container: "flex gap-xs items-center"
@@ -4,11 +4,6 @@ module Protos
4
4
  class Attributes
5
5
  # DOCS: A class that represents the attributes of a component. This would be
6
6
  # all html options except for `class` and `theme`.
7
- #
8
- # This class is responsible for safely merging in both user supplied and
9
- # default attributes. When a user adds { data: { controller: "foo" }} to
10
- # their component. This will merge the value in so that any default
11
- # controllers do not get overridden.
12
7
 
13
8
  def initialize(attrs = {}, **kwargs)
14
9
  @attrs = attrs.merge!(kwargs)
@@ -34,19 +29,7 @@ module Protos
34
29
  private
35
30
 
36
31
  def mix(hash, *hashes)
37
- hashes.each_with_object(hash) do |hash, result|
38
- result.merge!(hash) do |_key, a, b| # rubocop:disable Metrics/ParameterLists
39
- next a unless b
40
- next a if a == b
41
-
42
- case [a, b]
43
- in String, String then "#{a} #{b}"
44
- in Array, Array then a + b
45
- in Hash, Hash then mix(a, b)
46
- else b
47
- end
48
- end
49
- end
32
+ Mix.call(hash, *hashes)
50
33
  end
51
34
  end
52
35
  end
@@ -15,6 +15,12 @@ module Protos
15
15
 
16
16
  private
17
17
 
18
+ def default_attrs
19
+ {
20
+ aria_label: "breadcrumbs"
21
+ }
22
+ end
23
+
18
24
  def theme
19
25
  {
20
26
  container: "breadcrumbs"
@@ -5,14 +5,15 @@ module Protos
5
5
  class Title < Component
6
6
  # DOCS: The title of a collapse. This is the content that is always
7
7
  # visible and is used to toggle the collapse.
8
- option :id,
9
- type: Types::Coercible::String,
8
+
9
+ option :input_id,
10
+ type: Types::String | Types::Integer | Types::Nil,
10
11
  reader: false,
11
- default: -> { "" }
12
+ default: -> { }
12
13
 
13
14
  def view_template(&)
14
- if @id.size.positive?
15
- label(for: @id, **attrs, &)
15
+ if @input_id
16
+ label(for: @input_id.to_s, **attrs, &)
16
17
  else
17
18
  div(**attrs, &)
18
19
  end
@@ -6,19 +6,30 @@ module Protos
6
6
  # is visible at all times, and the content is only visible when expanded.
7
7
  # https://daisyui.com/components/collapse/
8
8
 
9
- option :checkbox, default: -> { false }, type: Types::Bool, reader: false
10
- option :id,
9
+ option :input_type, default: -> { :checkbox }, reader: false
10
+ option :input_id,
11
+ reader: false,
11
12
  default: -> { "collapse-#{SecureRandom.hex(4)}" },
12
13
  type: Types::String
13
14
 
14
15
  def view_template
15
16
  div(**attrs) do
16
- input(type: "checkbox", id:, autocomplete: :off) if @checkbox
17
+ if @input_type
18
+ input(
19
+ type: @input_type,
20
+ id: @input_id,
21
+ name: @input_id,
22
+ autocomplete: :off,
23
+ # form: "" prevents the radio button from being submitted if its
24
+ # within a form
25
+ form: ""
26
+ )
27
+ end
17
28
  yield if block_given?
18
29
  end
19
30
  end
20
31
 
21
- def title(*, **, &) = render Title.new(*, id:, **, &)
32
+ def title(*, **, &) = render Title.new(*, input_id: @input_id, **, &)
22
33
 
23
34
  def content(...) = render Content.new(...)
24
35
 
@@ -12,7 +12,7 @@ module Protos
12
12
  }
13
13
 
14
14
  def view_template(&block)
15
- div(**attrs) do
15
+ li(**attrs) do
16
16
  label(class: css[:label]) do
17
17
  div(class: css[:icon], &block) if block
18
18
  input(
@@ -7,7 +7,7 @@ module Protos
7
7
  # inside a Protos::Command::Group component.
8
8
 
9
9
  def view_template(&)
10
- h2(**attrs, &)
10
+ li { h2(**attrs, &) }
11
11
  end
12
12
 
13
13
  private
@@ -27,14 +27,14 @@ module Protos
27
27
  option :html_options, default: -> { {} }, reader: false
28
28
 
29
29
  # Adds non-defined options to the html_options hash
30
- def initialize(*args, **kwargs, &)
30
+ def initialize(*, **kwargs, &)
31
31
  defined_keys = self.class.dry_initializer.definitions.keys
32
32
  defined, undefined =
33
33
  kwargs
34
34
  .partition { |key, _| defined_keys.include?(key) }
35
35
  .map!(&:to_h)
36
36
 
37
- super(*args, html_options: undefined, **defined, &)
37
+ super(*, html_options: undefined, **defined, &)
38
38
  end
39
39
 
40
40
  private
@@ -6,11 +6,15 @@ module Protos
6
6
  # DOCS: The content that will be shown when you open the drawer using the
7
7
  # trigger. This would be hidden until triggered.
8
8
 
9
- option :id, type: Types::Coercible::String
9
+ option :input_id, reader: false, type: Types::String
10
10
 
11
11
  def view_template
12
12
  aside(**attrs) do
13
- label(for: id, aria_label: "close sidebar", class: css[:toggle])
13
+ label(
14
+ for: @input_id,
15
+ aria_label: "close sidebar",
16
+ class: css[:toggle]
17
+ )
14
18
  yield if block_given?
15
19
  end
16
20
  end
@@ -6,10 +6,10 @@ module Protos
6
6
  # DOCS: The trigger for a drawer. When this is clicked the side will open
7
7
  # and overlap the main content with a darker overlay.
8
8
 
9
- option :id, type: Types::Coercible::String
9
+ option :input_id, reader: false, type: Types::String
10
10
 
11
11
  def view_template(&)
12
- label(for: id, **attrs, &)
12
+ label(for: @input_id, **attrs, &)
13
13
  end
14
14
 
15
15
  private
data/lib/protos/drawer.rb CHANGED
@@ -7,20 +7,29 @@ module Protos
7
7
  # trigger is clicked.
8
8
  # https://daisyui.com/components/drawer/
9
9
 
10
- option :id, type: Types::Coercible::String
10
+ option :id,
11
+ reader: false,
12
+ type: Types::Coercible::String,
13
+ default: -> { "drawer-#{SecureRandom.hex(4)}" }
11
14
 
12
15
  def view_template
13
16
  div(**attrs) do
14
- input(id:, type: :checkbox, class: css[:toggle], autocomplete: :off)
17
+ input(
18
+ id: @id,
19
+ type: :checkbox,
20
+ class: css[:toggle],
21
+ autocomplete: :off,
22
+ form: ""
23
+ )
15
24
  yield if block_given?
16
25
  end
17
26
  end
18
27
 
19
28
  def content(...) = render Content.new(...)
20
29
 
21
- def side(*, **, &) = render Side.new(*, id:, **, &)
30
+ def side(*, **, &) = render Side.new(*, input_id: @id, **, &)
22
31
 
23
- def trigger(*, **, &) = render Trigger.new(*, id:, **, &)
32
+ def trigger(*, **, &) = render Trigger.new(*, input_id: @id, **, &)
24
33
 
25
34
  private
26
35
 
data/lib/protos/mix.rb ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protos
4
+ class Mix
5
+ # DOCS: This class is responsible for safely merging in both user supplied
6
+ # and default attributes. When a user adds { data: { controller: "foo" }} to
7
+ # their component. This will merge the value in so that any default
8
+ # controllers do not get overridden.
9
+
10
+ MERGEABLE_ATTRIBUTES = Set.new(%i[class data]).freeze
11
+
12
+ def self.call(...) = new.call(...)
13
+
14
+ def call(old_hash, *hashes)
15
+ hashes
16
+ .compact
17
+ .each_with_object(old_hash) do |new_hash, result|
18
+ merge(result, new_hash, top_level: true)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def merge(old_hash, new_hash, top_level: false) # rubocop:disable Metrics/PerceivedComplexity
25
+ old_hash.merge!(new_hash) do |key, old, new|
26
+ next old unless new
27
+ next old if old == new
28
+ next new if top_level && !MERGEABLE_ATTRIBUTES.include?(key)
29
+
30
+ case [old, new]
31
+ in String, String then "#{old} #{new}"
32
+ in Array, Array then old + new
33
+ in Hash, Hash then merge(old, new)
34
+ else new
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -7,7 +7,7 @@ module Protos
7
7
  # or click on to show the popover.
8
8
 
9
9
  def view_template(&)
10
- div(**attrs, &)
10
+ button(**attrs, &)
11
11
  end
12
12
 
13
13
  private
@@ -15,7 +15,8 @@ module Protos
15
15
  def default_attrs
16
16
  {
17
17
  data: { "protos--popover-target": "trigger" },
18
- tabindex: 0
18
+ tabindex: 0,
19
+ type: :button
19
20
  }
20
21
  end
21
22
  end
data/lib/protos/swap.rb CHANGED
@@ -8,7 +8,13 @@ module Protos
8
8
 
9
9
  def view_template
10
10
  label(**attrs) do
11
- input(type: :checkbox, class: css[:input], autocomplete: :off)
11
+ input(
12
+ type: :checkbox,
13
+ class: css[:input],
14
+ autocomplete: :off,
15
+ form: "",
16
+ aria_label: "swap"
17
+ )
12
18
  yield if block_given?
13
19
  end
14
20
  end
@@ -5,35 +5,24 @@ module Protos
5
5
  class Tab < Component
6
6
  # DOCS: A single tab in a tabs component
7
7
 
8
- option :id
9
- option :label
10
8
  option :active, default: -> { false }
11
9
  option :disabled, default: -> { false }
12
10
 
13
11
  def view_template(&)
14
- input(
15
- type: :radio,
16
- class: css[:input],
17
- name: id,
18
- role: :tab,
19
- aria_label: label,
20
- autocomplete: :off
21
- )
22
- div(**attrs, &)
12
+ button(**attrs, disabled:, &)
23
13
  end
24
14
 
25
15
  private
26
16
 
27
17
  def default_attrs
28
18
  {
29
- role: :tabpanel
19
+ role: :tab
30
20
  }
31
21
  end
32
22
 
33
23
  def theme
34
24
  {
35
- container: "tab-content",
36
- input: tokens(
25
+ container: tokens(
37
26
  "tab",
38
27
  disabled: "tab-disabled",
39
28
  active: "tab-active"
@@ -8,6 +8,7 @@ module Protos
8
8
  def view_template(&block)
9
9
  form(method: :dialog, class: css[:form]) do
10
10
  button(
11
+ aria_label: "close",
11
12
  autofocus: true,
12
13
  formmethod: :dialog,
13
14
  formnovalidate: true,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Protos
4
- VERSION = "0.4.3"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/protos.rb CHANGED
@@ -15,6 +15,7 @@ require_relative "protos/types"
15
15
  require_relative "protos/token_list"
16
16
  require_relative "protos/component"
17
17
  require_relative "protos/theme"
18
+ require_relative "protos/mix"
18
19
  require_relative "protos/attributes"
19
20
 
20
21
  # Components
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protos
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nolan J Tait
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-15 00:00:00.000000000 Z
11
+ date: 2024-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-core
@@ -149,6 +149,7 @@ files:
149
149
  - lib/protos/hero/overlay.rb
150
150
  - lib/protos/list.rb
151
151
  - lib/protos/list/item.rb
152
+ - lib/protos/mix.rb
152
153
  - lib/protos/modal.rb
153
154
  - lib/protos/modal/close_button.rb
154
155
  - lib/protos/modal/dialog.rb
@@ -220,7 +221,7 @@ requirements:
220
221
  - tailwindcss
221
222
  - daisyui
222
223
  - protos-stimulus
223
- rubygems_version: 3.5.17
224
+ rubygems_version: 3.5.18
224
225
  signing_key:
225
226
  specification_version: 4
226
227
  summary: A UI component library built with phlex and daisyui