anchor_view_components 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +61 -0
  5. data/app/assets/builds/anchor-view-components.css +1 -0
  6. data/app/assets/stylesheets/anchor-view-components.tailwind.css +8 -0
  7. data/app/assets/stylesheets/components/button.css +29 -0
  8. data/app/assets/stylesheets/components/toast.css +21 -0
  9. data/app/components/anchor/action_menu_component.html.erb +19 -0
  10. data/app/components/anchor/action_menu_component.rb +23 -0
  11. data/app/components/anchor/action_menu_controller.ts +21 -0
  12. data/app/components/anchor/anchor_view_components.ts +13 -0
  13. data/app/components/anchor/autocomplete_component.en.yml +3 -0
  14. data/app/components/anchor/autocomplete_component.html.erb +36 -0
  15. data/app/components/anchor/autocomplete_component.rb +42 -0
  16. data/app/components/anchor/autocomplete_controller.ts +37 -0
  17. data/app/components/anchor/badge_component.html.erb +8 -0
  18. data/app/components/anchor/badge_component.rb +27 -0
  19. data/app/components/anchor/banner_component.html.erb +9 -0
  20. data/app/components/anchor/banner_component.rb +28 -0
  21. data/app/components/anchor/breadcrumbs/item_component.html.erb +16 -0
  22. data/app/components/anchor/breadcrumbs/item_component.rb +15 -0
  23. data/app/components/anchor/breadcrumbs_component.html.erb +10 -0
  24. data/app/components/anchor/breadcrumbs_component.rb +17 -0
  25. data/app/components/anchor/button_component.html.erb +22 -0
  26. data/app/components/anchor/button_component.rb +76 -0
  27. data/app/components/anchor/component.rb +21 -0
  28. data/app/components/anchor/dialog_component.html.erb +42 -0
  29. data/app/components/anchor/dialog_component.rb +25 -0
  30. data/app/components/anchor/dialog_controller.ts +15 -0
  31. data/app/components/anchor/icon_component.html.erb +6 -0
  32. data/app/components/anchor/icon_component.rb +9 -0
  33. data/app/components/anchor/loading_indicator_component.html.erb +6 -0
  34. data/app/components/anchor/loading_indicator_component.rb +23 -0
  35. data/app/components/anchor/panel/body_component.html.erb +8 -0
  36. data/app/components/anchor/panel/body_component.rb +6 -0
  37. data/app/components/anchor/panel/footer_component.html.erb +9 -0
  38. data/app/components/anchor/panel/footer_component.rb +17 -0
  39. data/app/components/anchor/panel/header_component.html.erb +9 -0
  40. data/app/components/anchor/panel/header_component.rb +15 -0
  41. data/app/components/anchor/panel_component.html.erb +8 -0
  42. data/app/components/anchor/panel_component.rb +20 -0
  43. data/app/components/anchor/prose_component.html.erb +3 -0
  44. data/app/components/anchor/prose_component.rb +9 -0
  45. data/app/components/anchor/tab_bar/tab_component.html.erb +11 -0
  46. data/app/components/anchor/tab_bar/tab_component.rb +12 -0
  47. data/app/components/anchor/tab_bar_component.html.erb +10 -0
  48. data/app/components/anchor/tab_bar_component.rb +17 -0
  49. data/app/components/anchor/text_component.html.erb +8 -0
  50. data/app/components/anchor/text_component.rb +43 -0
  51. data/app/components/anchor/toast_component.html.erb +28 -0
  52. data/app/components/anchor/toast_component.rb +32 -0
  53. data/app/components/anchor/toast_controller.ts +21 -0
  54. data/app/helpers/anchor/fetch_or_fallback_helper.rb +20 -0
  55. data/app/helpers/anchor/view_helper.rb +35 -0
  56. data/lib/anchor/view_components/engine.rb +23 -0
  57. data/lib/anchor/view_components/version.rb +5 -0
  58. data/lib/anchor/view_components.rb +8 -0
  59. data/previews/anchor/action_menu_component_preview.rb +22 -0
  60. data/previews/anchor/badge_component_preview.rb +40 -0
  61. data/previews/anchor/banner_component_preview.rb +41 -0
  62. data/previews/anchor/banner_preview/with_banner.html.erb +11 -0
  63. data/previews/anchor/breadcrumbs_component_preview.rb +15 -0
  64. data/previews/anchor/button_component_preview.rb +81 -0
  65. data/previews/anchor/dialog_component_preview.rb +19 -0
  66. data/previews/anchor/dialog_preview/with_footer.html.erb +16 -0
  67. data/previews/anchor/icon_component_preview.rb +7 -0
  68. data/previews/anchor/loading_indicator_component_preview.rb +26 -0
  69. data/previews/anchor/panel_component_preview.rb +48 -0
  70. data/previews/anchor/tab_bar_component_preview.rb +15 -0
  71. data/previews/anchor/table_preview/default.html.erb +35 -0
  72. data/previews/anchor/text_component_preview.rb +76 -0
  73. data/previews/anchor/toast_component_preview.rb +34 -0
  74. metadata +156 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4a1787b8586a125669bb568cb6b27c5220354508e979264e44937291012380c7
4
+ data.tar.gz: f324e897a30739899f50a581c0242d312394ee79007de490d0ceb57616d003bb
5
+ SHA512:
6
+ metadata.gz: d8752971ee67126aa612523a7ff44257a007c732df8d13361ee3fc701a551b451b4e2130b82eaba165b7663b862c3a22cd60239c5250c68dadbf9d5aad2fa5de
7
+ data.tar.gz: ce47a488e3b0207870ca973717bbf5be2dfd2555da5d822ec2363438c69ea7f9b4e5d42d1654ee846ad1bc84d5a4f96dd810581864414335cb25fcc3d14f05dc
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2023-07-27
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [year] [fullname]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Anchor ViewComponents
2
+
3
+ [ViewComponents] for Buoy’s Anchor Design System.
4
+
5
+ [ViewComponents]: https://github.com/github/view_component
6
+
7
+ ## Dependencies
8
+
9
+ - [Stimulus]
10
+
11
+ [Stimulus]: https://stimulus.hotwired.dev
12
+
13
+ ## Installation
14
+
15
+ 1. Add `anchor_view_components` to your Gemfile and install:
16
+
17
+ ```bash
18
+ bundle add anchor_view_components && bundle install
19
+ ```
20
+
21
+ 2. Add `@buoysoftware/anchor-view-components` to your package.json and install:
22
+
23
+ ```bash
24
+ yarn add @buoysoftware/anchor-view-components && yarn
25
+ ```
26
+
27
+ 3. In `config/application.rb`, add the following statement:
28
+
29
+ ```rb
30
+ require "anchor/view_components/engine"
31
+ ```
32
+
33
+ 4. Add Anchor’s CSS to your application layout:
34
+
35
+ ```erb
36
+ <%= stylesheet_link_tag "anchor-view-components" %>
37
+ ```
38
+
39
+ 5. Import Anchor’s Stimulus controllers and register them:
40
+
41
+ ```js
42
+ import { registerAnchorControllers } from "@buoysoftware/anchor-view-components";
43
+
44
+ registerAnchorControllers(application);
45
+ ```
46
+
47
+ 6. Optionally, if you need Anchor’s Tailwind configuration to use its colors,
48
+ fonts, and other design system values, add the following to your project’s
49
+ `tailwind.config.js`:
50
+
51
+ ```js
52
+ presets: [require("@buoysoftware/anchor-view-components/tailwind.config.js")]
53
+ ```
54
+
55
+ ## Development
56
+
57
+ TODO
58
+
59
+ ## License
60
+
61
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1 @@
1
+ /*! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[popover]{position:fixed;z-index:2147483647;inset:0;padding:.25em;width:-moz-fit-content;width:fit-content;height:-moz-fit-content;height:fit-content;border:initial solid;border-color:initial;-o-border-image:initial;border-image:initial;background-color:canvas;color:initial;overflow:auto;margin:auto}[popover]:not(.\:popover-open){display:none}[popover]:is(dialog[open]){display:revert}@supports not (background-color:canvas){[popover]{background-color:#fff;color:#000}}@supports (width:-moz-fit-content){[popover]{width:-moz-fit-content;height:-moz-fit-content}}@supports not (inset:0){[popover]{top:0;left:0;right:0;bottom:0}}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.prose{color:#101112;max-width:65ch}.prose :where(p):not(:where([class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where([class~=lead]):not(:where([class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~=not-prose] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol>li):not(:where([class~=not-prose] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul>li):not(:where([class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(hr):not(:where([class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-left-width:.25rem;border-left-color:var(--tw-prose-quote-borders);quotes:"\201C""\201D""\2018""\2019";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~=not-prose] *)){font-weight:900;color:inherit}.prose :where(h2):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~=not-prose] *)){font-weight:800;color:inherit}.prose :where(h3):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(h4):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(img):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure>*):not(:where([class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose :where(code):not(:where([class~=not-prose] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em}.prose :where(code):not(:where([class~=not-prose] *)):before{content:"`"}.prose :where(code):not(:where([class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose :where(pre code):not(:where([class~=not-prose] *)){background-color:initial;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose] *)){width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose :where(tbody tr):not(:where([class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose] *)){vertical-align:initial}.prose :where(tfoot):not(:where([class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~=not-prose] *)){vertical-align:top}.prose{--tw-prose-body:#374151;--tw-prose-headings:#111827;--tw-prose-lead:#4b5563;--tw-prose-links:#111827;--tw-prose-bold:#111827;--tw-prose-counters:#6b7280;--tw-prose-bullets:#d1d5db;--tw-prose-hr:#e5e7eb;--tw-prose-quotes:#111827;--tw-prose-quote-borders:#e5e7eb;--tw-prose-captions:#6b7280;--tw-prose-code:#111827;--tw-prose-pre-code:#e5e7eb;--tw-prose-pre-bg:#1f2937;--tw-prose-th-borders:#d1d5db;--tw-prose-td-borders:#e5e7eb;--tw-prose-invert-body:#d1d5db;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#9ca3af;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#9ca3af;--tw-prose-invert-bullets:#4b5563;--tw-prose-invert-hr:#374151;--tw-prose-invert-quotes:#f3f4f6;--tw-prose-invert-quote-borders:#374151;--tw-prose-invert-captions:#9ca3af;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#d1d5db;--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:#4b5563;--tw-prose-invert-td-borders:#374151;font-size:.875rem;line-height:1rem}.prose :where(video):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure):not(:where([class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(li):not(:where([class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose :where(ul>li):not(:where([class~=not-prose] *)){padding-left:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose>ul>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>:first-child):not(:where([class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>:last-child):not(:where([class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(hr+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(thead th:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose] *)){padding:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose] *)){padding-left:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose] *)){padding-right:0}.prose :where(.prose>:first-child):not(:where([class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose] *)){margin-bottom:0}.btn{display:inline-flex;height:2.5rem;align-items:center;gap:.5rem;border-radius:.25rem;border-width:1px;--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));padding-left:1.25rem;padding-right:1.25rem;font-size:.75rem;line-height:1rem;font-weight:600;text-decoration-line:none}.btn:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.btn-primary{--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.btn-primary:hover{border-color:rgb(29 78 216/var(--tw-border-opacity));background-color:rgb(29 78 216/var(--tw-bg-opacity))}.btn-critical,.btn-primary:hover{--tw-border-opacity:1;--tw-bg-opacity:1}.btn-critical{border-color:rgb(191 47 29/var(--tw-border-opacity));background-color:rgb(191 47 29/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.btn-critical:hover{--tw-border-opacity:1;border-color:rgb(148 38 25/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(148 38 25/var(--tw-bg-opacity))}.btn-text{color:rgb(39 110 241/var(--tw-text-opacity))}.btn-text,.btn-text:hover{border-color:#0000;background-color:initial;--tw-text-opacity:1}.btn-text:hover{color:rgb(30 84 183/var(--tw-text-opacity))}.btn-small{height:1.75rem;padding-right:.75rem}.btn-small,.btn-with-starting-icon{padding-left:.75rem}.btn-with-ending-icon{padding-right:.75rem}.toast{align-items:center}.toast>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.toast{border-radius:.25rem;padding:1rem;--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.toast.\:popover-open{position:fixed;top:auto;bottom:2.5rem;margin-left:auto;margin-right:auto;display:flex;overflow:visible;border-width:0}.toast:popover-open{position:fixed;top:auto;bottom:2.5rem;margin-left:auto;margin-right:auto;display:flex;overflow:visible;border-width:0}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.bottom-0{bottom:0}.left-0{left:0}.z-10{z-index:10}.m-0{margin:0}.mx-2{margin-left:.5rem;margin-right:.5rem}.\!ml-10{margin-left:2.5rem!important}.ml-auto{margin-left:auto}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.contents{display:contents}.hidden{display:none}.h-12{height:3rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.max-h-\[200px\]{max-height:200px}.w-12{width:3rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-\[36rem\]{width:36rem}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-4{gap:1rem}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-4{border-width:4px}.border-\[1\.5px\]{border-width:1.5px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-grey-100{--tw-border-opacity:1;border-color:rgb(16 17 18/var(--tw-border-opacity))}.border-subdued{--tw-border-opacity:1;border-color:rgb(186 190 195/var(--tw-border-opacity))}.border-b-transparent{border-bottom-color:#0000}.bg-critical-subdued{--tw-bg-opacity:1;background-color:rgb(255 222 217/var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.bg-green-700{--tw-bg-opacity:1;background-color:rgb(21 128 61/var(--tw-bg-opacity))}.bg-informational-subdued{--tw-bg-opacity:1;background-color:rgb(212 226 252/var(--tw-bg-opacity))}.bg-neutral{--tw-bg-opacity:1;background-color:rgb(235 237 240/var(--tw-bg-opacity))}.bg-red-700{--tw-bg-opacity:1;background-color:rgb(185 28 28/var(--tw-bg-opacity))}.bg-success-subdued{--tw-bg-opacity:1;background-color:rgb(218 240 227/var(--tw-bg-opacity))}.bg-warning-subdued{--tw-bg-opacity:1;background-color:rgb(255 242 217/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-yellow-600{--tw-bg-opacity:1;background-color:rgb(202 138 4/var(--tw-bg-opacity))}.p-0{padding:0}.p-6{padding:1.5rem}.px-0{padding-left:0;padding-right:0}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-1{padding-bottom:.25rem}.pb-6{padding-bottom:1.5rem}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:1.75rem}.text-3xl{font-size:2rem;line-height:2.5rem}.text-4xl{font-size:2.5rem;line-height:3rem}.text-base{font-size:.875rem;line-height:1rem}.text-lg{font-size:1rem;line-height:1.25rem}.text-sm{font-size:.75rem;line-height:1rem}.text-xl{font-size:1.25rem;line-height:1.5rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-4{line-height:1rem}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-grey-50{--tw-text-opacity:1;color:rgb(156 161 165/var(--tw-text-opacity))}.text-primary{--tw-text-opacity:1;color:rgb(16 17 18/var(--tw-text-opacity))}.text-tertiary{--tw-text-opacity:1;color:rgb(156 161 165/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.opacity-70{opacity:.7}.shadow-\[inset_0_-1px\]{--tw-shadow:inset 0 -1px;--tw-shadow-colored:inset 0 -1px var(--tw-shadow-color)}.shadow-\[inset_0_-1px\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-gray-400{--tw-shadow-color:#9ca3af;--tw-shadow:var(--tw-shadow-colored)}.backdrop\:bg-grey-100\/50::backdrop{background-color:#10111280}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:inset-x-0:after{content:var(--tw-content);left:0;right:0}.after\:-bottom-1:after{content:var(--tw-content);bottom:-.25rem}.after\:block:after{content:var(--tw-content);display:block}.after\:h-1:after{content:var(--tw-content);height:.25rem}.after\:bg-blue-500:after{content:var(--tw-content);--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity))}.hover\:bg-grey-10:hover{--tw-bg-opacity:1;background-color:rgb(245 246 247/var(--tw-bg-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.hover\:text-grey-70:hover{--tw-text-opacity:1;color:rgb(98 102 106/var(--tw-text-opacity))}.group[aria-expanded][data-empty=true][data-loading=false] .group-aria-\[expanded\]\[data-empty\=\'true\'\]\[data-loading\=\'false\'\]\:block,.group[aria-expanded][data-loading=true] .group-aria-\[expanded\]\[data-loading\=\'true\'\]\:block{display:block}.group[data-empty=true] .group-data-\[empty\=\'true\'\]\:hidden,.group[data-loading=true] .group-data-\[loading\=\'true\'\]\:hidden{display:none}.popover-open\:block:popover-open{display:block}.\[\&\>\[aria-selected\=\'true\'\]\]\:bg-blue-20>[aria-selected=true]{--tw-bg-opacity:1;background-color:rgb(212 226 252/var(--tw-bg-opacity))}.\[\&\>li\]\:cursor-pointer>li{cursor:pointer}.\[\&\>li\]\:px-3>li{padding-left:.75rem;padding-right:.75rem}.\[\&\>li\]\:py-2>li{padding-top:.5rem;padding-bottom:.5rem}
@@ -0,0 +1,8 @@
1
+ @import "tailwindcss/base";
2
+ @import "tailwindcss/components";
3
+ @import "tailwindcss/utilities";
4
+
5
+ @import "@oddbird/popover-polyfill/dist/popover" layer(base);
6
+
7
+ @import "components/button" layer(components);
8
+ @import "components/toast" layer(components);
@@ -0,0 +1,29 @@
1
+ @layer components {
2
+ .btn {
3
+ @apply inline-flex items-center gap-2 rounded bg-white hover:bg-gray-100 border h-10 px-5 text-sm font-semibold no-underline;
4
+ }
5
+
6
+ .btn-primary {
7
+ @apply bg-blue-600 border-blue-600 hover:bg-blue-700 hover:border-blue-700 text-white;
8
+ }
9
+
10
+ .btn-critical {
11
+ @apply bg-red-60 border-red-60 hover:bg-red-70 hover:border-red-70 text-white;
12
+ }
13
+
14
+ .btn-text {
15
+ @apply bg-transparent border-transparent hover:bg-transparent hover:border-transparent text-blue-50 hover:text-blue-60;
16
+ }
17
+
18
+ .btn-small {
19
+ @apply h-7 px-3;
20
+ }
21
+
22
+ .btn-with-starting-icon {
23
+ @apply pl-3;
24
+ }
25
+
26
+ .btn-with-ending-icon {
27
+ @apply pr-3;
28
+ }
29
+ }
@@ -0,0 +1,21 @@
1
+ @layer components {
2
+ .toast {
3
+ @apply p-4 rounded items-center space-x-2 shadow-lg;
4
+ }
5
+
6
+ /*
7
+ Styling for polyfilled version of `:popover-open` pseudo-class
8
+ https://github.com/oddbird/popover-polyfill
9
+ */
10
+ .toast.\:popover-open {
11
+ @apply flex fixed top-auto bottom-10 mx-auto border-0 overflow-visible;
12
+ }
13
+
14
+ /*
15
+ Styling for standards version of `:popover-open` pseudo-class
16
+ https://developer.mozilla.org/en-US/docs/Web/CSS/:popover-open
17
+ */
18
+ .toast:popover-open {
19
+ @apply flex fixed top-auto bottom-10 mx-auto border-0 overflow-visible;
20
+ }
21
+ }
@@ -0,0 +1,19 @@
1
+ <%= tag.div(
2
+ class: "contents",
3
+ data: { controller: "action-menu" },
4
+ ) do %>
5
+ <%= show_button if show_button? %>
6
+
7
+ <%= tag.div(
8
+ class: "absolute left-0 w-max m-0 mt-1 px-0 py-1 bg-white rounded border border-subdued popover-open:block",
9
+ data: { action: "toggle->action-menu#positionPopover" },
10
+ id: @menu_id,
11
+ popover: "auto",
12
+ ) do %>
13
+ <ul>
14
+ <% items.each do |item| %>
15
+ <%= tag.li item, class: "px-3 py-1 hover:bg-grey-10" %>
16
+ <% end %>
17
+ </ul>
18
+ <% end %>
19
+ <% end %>
@@ -0,0 +1,23 @@
1
+ module Anchor
2
+ class ActionMenuComponent < Component
3
+ renders_one :show_button, -> do
4
+ ButtonComponent.new(
5
+ data: { action_menu_target: "button" },
6
+ popovertarget: @menu_id
7
+ )
8
+ end
9
+ renders_many :items
10
+
11
+ def initialize
12
+ @menu_id = self.class.generate_id
13
+
14
+ super
15
+ end
16
+
17
+ private
18
+
19
+ def render?
20
+ items.present?
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { computePosition } from "@floating-ui/dom";
3
+
4
+ export default class extends Controller<HTMLDivElement> {
5
+ static targets = ["button"];
6
+
7
+ declare readonly buttonTarget: HTMLButtonElement;
8
+
9
+ positionPopover = (event: { target: HTMLDivElement }): void => {
10
+ const popover = event.target;
11
+
12
+ computePosition(this.buttonTarget, popover, {
13
+ placement: "bottom-start",
14
+ }).then(({ x, y }) => {
15
+ Object.assign(popover.style, {
16
+ left: `${x}px`,
17
+ top: `${y + 4}px`,
18
+ });
19
+ });
20
+ };
21
+ }
@@ -0,0 +1,13 @@
1
+ import "@oddbird/popover-polyfill";
2
+
3
+ import ActionMenuController from "./action_menu_controller";
4
+ import AutocompleteController from "./autocomplete_controller";
5
+ import DialogController from "./dialog_controller";
6
+ import ToastController from "./toast_controller";
7
+
8
+ export function registerAnchorControllers(application) {
9
+ application.register("action-menu", ActionMenuController);
10
+ application.register("autocomplete", AutocompleteController);
11
+ application.register("dialog", DialogController);
12
+ application.register("toast", ToastController);
13
+ }
@@ -0,0 +1,3 @@
1
+ en:
2
+ no_options: "No options"
3
+ loading: "Loading..."
@@ -0,0 +1,36 @@
1
+ <div
2
+ data-controller="autocomplete"
3
+ data-autocomplete-url-value="<%= src %>"
4
+ role="combobox"
5
+ class="group relative"
6
+ >
7
+ <%= form_builder.input(
8
+ "#{name}_search".to_sym,
9
+ input_html: {
10
+ data: {
11
+ autocomplete_target: "input",
12
+ autocomplete_min_length_value: 0,
13
+ action: "click->autocomplete#onInputClick",
14
+ }.merge(input_data),
15
+ readonly:,
16
+ }
17
+ ) %>
18
+ <div class="hidden">
19
+ <%= form_builder.input(
20
+ "#{name}_id".to_sym,
21
+ input_html: {
22
+ data: { autocomplete_target: "hidden" }
23
+ }) %>
24
+ </div>
25
+ <ul
26
+ data-autocomplete-target="results"
27
+ class="<%= list_box_classes %> group-data-[empty='true']:hidden group-data-[loading='true']:hidden [&>li]:py-2 [&>li]:px-3 [&>li]:cursor-pointer [&>[aria-selected='true']]:bg-blue-20"
28
+ >
29
+ </ul>
30
+ <div class="<%= list_box_classes %> text-center text-gray-500 hidden group-aria-[expanded][data-empty='true'][data-loading='false']:block">
31
+ <%= t(".no_options") %>
32
+ </div>
33
+ <div class="<%= list_box_classes %> text-center text-gray-500 hidden group-aria-[expanded][data-loading='true']:block">
34
+ <%= t(".loading") %>
35
+ </div>
36
+ </div>
@@ -0,0 +1,42 @@
1
+ module Anchor
2
+ class AutocompleteComponent < Component
3
+ LIST_BOX_CLASSES = %w(
4
+ absolute
5
+ bg-white
6
+ border
7
+ border-gray-300
8
+ max-h-[200px]
9
+ mt-2
10
+ overflow-y-auto
11
+ py-2
12
+ rounded-md
13
+ shadow-lg
14
+ w-full
15
+ z-10
16
+ ).join(" ").freeze
17
+
18
+ def initialize(
19
+ form_builder:,
20
+ name:,
21
+ src:,
22
+ readonly: false,
23
+ input_data: {}
24
+ )
25
+ @form_builder = form_builder
26
+ @name = name
27
+ @readonly = readonly
28
+ @input_data = input_data
29
+ @src = src
30
+
31
+ super
32
+ end
33
+
34
+ def list_box_classes
35
+ LIST_BOX_CLASSES
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :form_builder, :name, :readonly, :input_data, :src
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ import type { ControllerConstructor } from "@hotwired/stimulus"
2
+ import BaseAutocompleteController from "stimulus-autocomplete"
3
+
4
+ class AutocompleteController extends BaseAutocompleteController {
5
+ connect(): void {
6
+ super.connect()
7
+ this.element.addEventListener("loadstart", this.onLoadStart)
8
+ this.element.addEventListener("loadend", this.onLoadEnd)
9
+ }
10
+
11
+ disconnect(): void {
12
+ super.disconnect()
13
+ this.element.removeEventListener("loadstart", this.onLoadStart)
14
+ this.element.removeEventListener("loadend", this.onLoadEnd)
15
+ }
16
+
17
+ onInputClick = (): void => {
18
+ const query = this.inputTarget.value.trim()
19
+
20
+ if (query.length >= this.minLengthValue) {
21
+ this.fetchResults(query)
22
+ } else {
23
+ this.hideAndRemoveOptions()
24
+ }
25
+ }
26
+
27
+ onLoadStart = (): void => {
28
+ this.element.dataset.loading = true
29
+ }
30
+
31
+ onLoadEnd = (): void => {
32
+ this.element.dataset.loading = false
33
+ this.element.dataset.empty = this.resultsTarget.childElementCount === 0
34
+ }
35
+ }
36
+
37
+ export default AutocompleteController as unknown as ControllerConstructor
@@ -0,0 +1,8 @@
1
+ <%= tag.span(
2
+ class: class_names(
3
+ "inline-block px-2 py-0.5 rounded-full text-xs",
4
+ @variant,
5
+ ),
6
+ ) do %>
7
+ <%= content %>
8
+ <% end %>
@@ -0,0 +1,27 @@
1
+ module Anchor
2
+ class BadgeComponent < Component
3
+ VARIANT_DEFAULT = :neutral
4
+ VARIANT_MAPPINGS = {
5
+ VARIANT_DEFAULT => "bg-neutral text-base",
6
+ :informational => "bg-informational-subdued text-base",
7
+ :success => "bg-success-subdued text-base",
8
+ :critical => "bg-critical-subdued text-base",
9
+ :warning => "bg-warning-subdued text-base",
10
+ }.freeze
11
+ VARIANT_OPTIONS = VARIANT_MAPPINGS.keys
12
+
13
+ def initialize(variant: VARIANT_DEFAULT)
14
+ @variant = VARIANT_MAPPINGS[
15
+ fetch_or_fallback(VARIANT_OPTIONS, variant, VARIANT_DEFAULT)
16
+ ]
17
+
18
+ super
19
+ end
20
+
21
+ private
22
+
23
+ def render?
24
+ content.present?
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ <%= tag.div(
2
+ class: class_names(
3
+ "px-4 py-3 rounded",
4
+ @variant,
5
+ @classes,
6
+ ),
7
+ ) do %>
8
+ <%= content %>
9
+ <% end %>
@@ -0,0 +1,28 @@
1
+ module Anchor
2
+ class BannerComponent < Component
3
+ VARIANT_DEFAULT = :neutral
4
+ VARIANT_MAPPINGS = {
5
+ VARIANT_DEFAULT => "bg-neutral",
6
+ :informational => "bg-informational-subdued",
7
+ :success => "bg-success-subdued",
8
+ :critical => "bg-critical-subdued",
9
+ :warning => "bg-warning-subdued",
10
+ }.freeze
11
+ VARIANT_OPTIONS = VARIANT_MAPPINGS.keys
12
+
13
+ def initialize(variant: VARIANT_DEFAULT, classes: nil)
14
+ @variant = VARIANT_MAPPINGS[
15
+ fetch_or_fallback(VARIANT_OPTIONS, variant, VARIANT_DEFAULT)
16
+ ]
17
+ @classes = classes
18
+
19
+ super
20
+ end
21
+
22
+ private
23
+
24
+ def render?
25
+ content.present?
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ <%= tag.li(
2
+ aria: {
3
+ current: { page: @active }
4
+ },
5
+ ) do %>
6
+ <%= link_to(
7
+ @href,
8
+ class: "flex"
9
+ ) do %>
10
+ <% if show_separator %>
11
+ <span aria-hidden="true" class="mx-2 text-tertiary">/</span>
12
+ <% end %>
13
+
14
+ <%= content %>
15
+ <% end %>
16
+ <% end %>
@@ -0,0 +1,15 @@
1
+ module Anchor
2
+ module Breadcrumbs
3
+ class ItemComponent < Component
4
+ attr_accessor :active, :show_separator
5
+
6
+ def initialize(href:, active: false, show_separator: true)
7
+ @href = href
8
+ @active = active
9
+ @show_separator = show_separator
10
+
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ <%= tag.nav aria: { label: @label } do %>
2
+ <ol class="flex items-center">
3
+ <% items.first.show_separator = false %>
4
+ <% items.last.active = true %>
5
+
6
+ <% items.each do |item| %>
7
+ <%= item %>
8
+ <% end %>
9
+ </ol>
10
+ <% end %>
@@ -0,0 +1,17 @@
1
+ module Anchor
2
+ class BreadcrumbsComponent < Component
3
+ renders_many :items, Breadcrumbs::ItemComponent
4
+
5
+ def initialize(label: "Breadcrumb")
6
+ @label = label
7
+
8
+ super
9
+ end
10
+
11
+ private
12
+
13
+ def render?
14
+ items.present?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ <%= content_tag(
2
+ @tag,
3
+ type: @type,
4
+ href: @href,
5
+ data: @data,
6
+ form: @form,
7
+ popovertarget: @popovertarget,
8
+ popovertargetaction: @popovertargetaction,
9
+ class: class_names(
10
+ @size,
11
+ @variant,
12
+ @classes,
13
+ "btn-with-starting-icon" => starting_icon?,
14
+ "btn-with-ending-icon" => ending_icon?,
15
+ ),
16
+ ) do %>
17
+ <%= starting_icon if starting_icon? %>
18
+
19
+ <%= content %>
20
+
21
+ <%= ending_icon if ending_icon? %>
22
+ <% end %>
@@ -0,0 +1,76 @@
1
+ module Anchor
2
+ class ButtonComponent < Component
3
+ TAG_DEFAULT = :button
4
+ TAG_OPTIONS = [TAG_DEFAULT, :a].freeze
5
+
6
+ TYPE_DEFAULT = :button
7
+ TYPE_OPTIONS = [TYPE_DEFAULT, :reset, :submit].freeze
8
+
9
+ VARIANT_DEFAULT = :basic
10
+ VARIANT_MAPPINGS = {
11
+ VARIANT_DEFAULT => "btn",
12
+ :primary => "btn btn-primary",
13
+ :critical => "btn btn-critical",
14
+ :text => "btn btn-text",
15
+ }.freeze
16
+ VARIANT_OPTIONS = VARIANT_MAPPINGS.keys
17
+
18
+ SIZE_DEFAULT = nil
19
+ SIZE_MAPPINGS = {
20
+ small: "btn-small",
21
+ }.freeze
22
+ SIZE_OPTIONS = SIZE_MAPPINGS.keys << SIZE_DEFAULT
23
+
24
+ renders_one :starting_icon, ->(icon:) do
25
+ Anchor::IconComponent.new(
26
+ icon:,
27
+ classes: "w-5 h-5",
28
+ data: { testid: "button-starting-icon" }
29
+ )
30
+ end
31
+ renders_one :ending_icon, ->(icon:) do
32
+ Anchor::IconComponent.new(
33
+ icon:,
34
+ classes: "w-5 h-5",
35
+ data: { testid: "button-ending-icon" }
36
+ )
37
+ end
38
+
39
+ def initialize(
40
+ tag: TAG_DEFAULT,
41
+ type: TYPE_DEFAULT,
42
+ href: nil,
43
+ size: SIZE_DEFAULT,
44
+ variant: VARIANT_DEFAULT,
45
+ data: {},
46
+ form: nil,
47
+ popovertarget: nil,
48
+ popovertargetaction: nil,
49
+ **kwargs
50
+ )
51
+ @tag = fetch_or_fallback(TAG_OPTIONS, tag, TAG_DEFAULT)
52
+ if @tag == :button
53
+ @type = fetch_or_fallback(
54
+ TYPE_OPTIONS, type, TYPE_DEFAULT
55
+ )
56
+ end
57
+ @data = data
58
+ @href = href if @tag == :a
59
+ @size = SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, SIZE_DEFAULT)]
60
+ @variant = VARIANT_MAPPINGS[
61
+ fetch_or_fallback(VARIANT_OPTIONS, variant, VARIANT_DEFAULT)
62
+ ]
63
+ @form = form
64
+ @popovertarget = popovertarget
65
+ @popovertargetaction = popovertargetaction
66
+
67
+ super
68
+ end
69
+
70
+ private
71
+
72
+ def render?
73
+ content.present?
74
+ end
75
+ end
76
+ end