primer_view_components 0.0.92 → 0.0.93

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +2 -2
  4. data/app/assets/javascripts/primer_view_components.js +1 -1
  5. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  6. data/app/assets/styles/primer_view_components.css +1 -0
  7. data/app/assets/styles/primer_view_components.css.map +1 -1
  8. data/app/components/primer/alpha/dialog/body.rb +25 -0
  9. data/app/components/primer/alpha/dialog/footer.rb +31 -0
  10. data/app/components/primer/alpha/dialog/header.html.erb +15 -0
  11. data/app/components/primer/alpha/dialog/header.rb +37 -0
  12. data/app/components/primer/alpha/dialog.html.erb +12 -0
  13. data/app/components/primer/alpha/dialog.rb +160 -0
  14. data/app/components/primer/alpha/modal-dialog-element.d.ts +1 -1
  15. data/app/components/primer/alpha/modal-dialog-element.js +2 -3
  16. data/app/components/primer/alpha/modal-dialog-element.ts +148 -0
  17. data/app/components/primer/alpha/toggle-switch-element.js +2 -0
  18. data/app/components/primer/alpha/toggle-switch-element.ts +2 -1
  19. data/app/components/primer/alpha/tool-tip-element.ts +0 -1
  20. data/app/components/primer/beta/button.html.erb +23 -0
  21. data/app/components/primer/beta/button.pcss +332 -0
  22. data/app/components/primer/beta/button.rb +189 -0
  23. data/app/components/primer/beta/icon_button.html.erb +6 -0
  24. data/app/components/primer/beta/icon_button.rb +104 -0
  25. data/app/components/primer/clipboard_copy_component.ts +1 -1
  26. data/app/components/primer/experimental/action-bar-element.d.ts +14 -0
  27. data/app/components/primer/experimental/action-bar-element.js +139 -0
  28. data/app/components/primer/experimental/action-menu-element.d.ts +31 -0
  29. data/app/components/primer/experimental/action-menu-element.js +334 -0
  30. data/app/components/primer/experimental/overflow-menu-element.d.ts +13 -0
  31. data/app/components/primer/experimental/overflow-menu-element.js +113 -0
  32. data/app/components/primer/primer.d.ts +1 -0
  33. data/app/components/primer/primer.js +1 -0
  34. data/app/components/primer/primer.pcss +1 -0
  35. data/app/components/primer/primer.ts +1 -0
  36. data/lib/postcss_mixins/focusBoxShadowInset.pcss +6 -0
  37. data/lib/postcss_mixins/focusOutline.pcss +5 -0
  38. data/lib/postcss_mixins/focusOutlineOnEmphasis.pcss +6 -0
  39. data/lib/postcss_mixins/minTouchTarget.js +20 -0
  40. data/lib/postcss_mixins/targetBoxShadow.pcss +6 -0
  41. data/lib/primer/view_components/linters/argument_mappers/base.rb +1 -1
  42. data/lib/primer/view_components/version.rb +1 -1
  43. data/lib/tasks/docs.rake +3 -8
  44. data/static/arguments.yml +113 -0
  45. data/static/audited_at.json +6 -0
  46. data/static/constants.json +107 -0
  47. data/static/statuses.json +6 -0
  48. metadata +25 -5
  49. data/app/components/primer/alpha/segmented-control-element.d.ts +0 -8
  50. data/app/components/primer/alpha/segmented-control-element.js +0 -28
  51. data/static/classes.yml +0 -230
@@ -0,0 +1 @@
1
+ :root{--primer-duration-fast:80ms;--primer-easing-easeInOut:cubic-bezier(0.65,0,0.35,1)}.Button{align-items:center;background-color:transparent;border:1px solid;border:var(--primer-borderWidth-thin,1px) solid;border-color:transparent;border-radius:6px;border-radius:var(--primer-borderRadius-medium,6px);color:var(--color-btn-text);cursor:pointer;display:flex;flex-direction:row;font-size:14px;font-size:var(--primer-text-body-size-medium,14px);font-weight:500;font-weight:var(--base-text-weight-medium,500);gap:8px;gap:var(--primer-control-medium-gap,8px);height:32px;height:var(--primer-control-medium-size,32px);justify-content:space-between;padding:0 12px;padding:0 var(--primer-control-medium-paddingInline-normal,12px);position:relative;text-align:center;transition:80ms cubic-bezier(.65,0,.35,1);transition:var(--primer-duration-fast) var(--primer-easing-easeInOut);transition-property:color,fill,background-color,border-color;-webkit-user-select:none;-moz-user-select:none;user-select:none}@media (pointer:course){.Button:before{content:"";height:100%;left:50%;min-height:48px;min-width:48px;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}}.Button:hover{transition-duration:80ms;transition-duration:var(--primer-duration-fast)}.Button.Button--active,.Button:active{transition:none}.Button.Button--disabled,.Button:disabled,.Button[aria-disabled=true]{box-shadow:none;cursor:not-allowed}.Button-withTooltip{display:inline-block;position:relative}a.Button,summary.Button{display:inline-flex}a.Button:hover,summary.Button:hover{text-decoration:none}.Button-content{align-items:center;display:grid;flex:1 0 auto;grid-template-areas:"leadingVisual text trailingVisual";grid-template-columns:-webkit-min-content minmax(0,auto) -webkit-min-content;grid-template-columns:min-content minmax(0,auto) min-content;place-content:center}.Button-content>:not(:last-child){margin-right:8px;margin-right:var(--primer-control-medium-gap,8px)}.Button-content--alignStart{justify-content:start}.Button-visual{display:flex;pointer-events:none}.Button-label{grid-area:text;line-height:1.42857;line-height:var(--primer-text-body-lineHeight-medium,1.42857);white-space:nowrap}.Button-leadingVisual{grid-area:leadingVisual}.Button-trailingVisual{grid-area:trailingVisual}.Button-trailingAction{margin-right:-4px;margin-right:calc(var(--base-size-4, 4px)*-1)}.Button--small{font-size:12px;font-size:var(--primer-text-body-size-small,12px);gap:4px;gap:var(--primer-control-small-gap,4px);height:28px;height:var(--primer-control-small-size,28px);padding:0 12px;padding:0 var(--primer-control-small-paddingInline-normal,12px);.Button-label{line-height:1.66667;line-height:var(--primer-text-body-lineHeight-small,1.66667)}.Button-content>:not(:last-child){margin-right:4px;margin-right:var(--primer-control-small-gap,4px)}}.Button--large{gap:8px;gap:var(--primer-control-large-gap,8px);height:40px;height:var(--primer-control-large-size,40px);padding:0 12px;padding:0 var(--primer-control-large-paddingInline-normal,12px);.Button-label{line-height:1.5;line-height:var(--primer-text-body-lineHeight-large,1.5)}.Button-content>:not(:last-child){margin-right:8px;margin-right:var(--primer-control-large-gap,8px)}}.Button--fullWidth{width:100%}.Button--primary{fill:var(--color-btn-primary-icon);background-color:var(--color-btn-primary-bg);border-color:var(--color-btn-primary-border);box-shadow:var(--color-btn-primary-shadow),var(--color-btn-primary-inset-shadow);color:var(--color-btn-primary-text)}.Button--primary:hover{background-color:var(--color-btn-primary-hover-bg);border-color:var(--color-btn-primary-hover-border)}.Button--primary:focus{box-shadow:inset 0 0 0 3px var(--color-fg-on-emphasis);outline:2px solid var(--color-accent-fg);outline-offset:-2px}.Button--primary:focus:not(:focus-visible){box-shadow:none;outline:1px solid transparent}.Button--primary:focus-visible{box-shadow:inset 0 0 0 3px var(--color-fg-on-emphasis);outline:2px solid var(--color-accent-fg);outline-offset:-2px}.Button--primary.Button--pressed,.Button--primary:active,.Button--primary[aria-pressed=true]{background-color:var(--color-btn-primary-selected-bg);box-shadow:var(--color-btn-primary-selected-shadow)}.Button--primary.Button--disabled,.Button--primary:disabled,.Button--primary[aria-disabled=true]{fill:var(--color-btn-primary-disabled-text);background-color:var(--color-btn-primary-disabled-bg);border-color:var(--color-btn-primary-disabled-border);color:var(--color-btn-primary-disabled-text)}.Button--secondary{fill:var(--color-fg-muted);background-color:var(--color-btn-bg);border-color:var(--color-btn-border);box-shadow:var(--color-btn-shadow),var(--color-btn-inset-shadow);color:var(--color-btn-text)}.Button--secondary:hover{background-color:var(--color-btn-hover-bg);border-color:var(--color-btn-hover-border)}.Button--secondary.Button--active,.Button--secondary:active{background-color:var(--color-btn-active-bg);border-color:var(--color-btn-active-border)}.Button--secondary.Button--pressed,.Button--secondary[aria-pressed=true]{background-color:var(--color-btn-selected-bg);box-shadow:var(--color-primer-shadow-inset)}.Button--secondary.Button--disabled,.Button--secondary:disabled,.Button--secondary[aria-disabled=true]{fill:var(--color-primer-fg-disabled);background-color:var(--color-btn-bg);border-color:var(--color-btn-border);color:var(--color-primer-fg-disabled)}.Button--invisible{fill:var(--color-fg-default);border:none;color:var(--color-fg-default)}.Button--invisible:hover{background-color:var(--color-action-list-item-default-hover-bg)}.Button--invisible.Button--active,.Button--invisible.Button--pressed,.Button--invisible:active,.Button--invisible[aria-pressed=true]{background-color:var(--color-action-list-item-default-active-bg)}.Button--invisible.Button--disabled,.Button--invisible:disabled,.Button--invisible[aria-disabled=true]{fill:var(--color-primer-fg-disabled);background-color:var(--color-btn-bg);border-color:var(--color-btn-border);color:var(--color-primer-fg-disabled)}.Button--invisible{.Button-label:not(:only-child){color:var(--color-btn-text)}.Button-content:not(:only-child){.Button-label{color:var(--color-btn-text)}}}.Button--danger{fill:var(--color-btn-danger-icon);background-color:var(--color-btn-bg);border-color:var(--color-btn-border);box-shadow:var(--color-btn-shadow),var(--color-btn-inset-shadow);color:var(--color-btn-danger-text)}.Button--danger:hover{fill:var(--color-btn-danger-hover-text);background-color:var(--color-btn-danger-hover-bg);border-color:var(--color-btn-danger-hover-border);box-shadow:var(--color-btn-danger-hover-shadow),var(--color-btn-danger-hover-inset-shadow);color:var(--color-btn-danger-hover-text)}.Button--danger.Button--pressed,.Button--danger:active,.Button--danger[aria-pressed=true]{fill:var(--color-btn-danger-selected-text);background-color:var(--color-btn-danger-selected-bg);border-color:var(--color-btn-danger-selected-border);box-shadow:var(--color-btn-danger-selected-shadow);color:var(--color-btn-danger-selected-text)}.Button--danger.disabled,.Button--danger:disabled,.Button--danger[aria-disabled=true]{fill:var(--color-btn-danger-disabled-text);background-color:var(--color-btn-danger-disabled-bg);border-color:var(--color-btn-border);color:var(--color-btn-danger-disabled-text)}.Button--iconOnly{display:grid;padding:initial;place-content:center;width:32px;width:var(--primer-control-medium-size,32px)}.Button--iconOnly.Button--small{width:28px;width:var(--primer-control-small-size,28px)}.Button--iconOnly.Button--large{width:40px;width:var(--primer-control-large-size,40px)}
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"primer_view_components.css"}
1
+ {"version":3,"sources":["../../components/primer/beta/button.pcss","<no source>","../../../lib/postcss_mixins/focusOutlineOnEmphasis.pcss"],"names":[],"mappings":"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","file":"primer_view_components.css","sourcesContent":["/* CSS for Button */\n/* temporary, pre primitives release */\n:root {\n --primer-duration-fast: 80ms;\n --primer-easing-easeInOut: cubic-bezier(0.65, 0, 0.35, 1);\n}\n\n/* base button */\n.Button {\n position: relative;\n font-size: var(--primer-text-body-size-medium, 14px);\n font-weight: var(--base-text-weight-medium, 500);\n cursor: pointer;\n user-select: none;\n background-color: transparent;\n border: var(--primer-borderWidth-thin, 1px) solid;\n border-color: transparent;\n border-radius: var(--primer-borderRadius-medium, 6px);\n color: var(--color-btn-text);\n transition: var(--primer-duration-fast) var(--primer-easing-easeInOut);\n transition-property: color, fill, background-color, border-color;\n text-align: center;\n height: var(--primer-control-medium-size, 32px);\n padding: 0 var(--primer-control-medium-paddingInline-normal, 12px);\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n gap: var(--primer-control-medium-gap, 8px);\n\n /* mobile friendly sizing */\n @media (pointer: course) {\n &::before {\n @mixin minTouchTarget 48px, 48px;\n }\n }\n\n /* base states */\n\n &:hover {\n transition-duration: var(--primer-duration-fast);\n }\n\n &:active,\n &.Button--active {\n transition: none;\n }\n\n &:disabled,\n &.Button--disabled,\n &[aria-disabled='true'] {\n cursor: not-allowed;\n box-shadow: none;\n }\n\n /* &:focus {\n @mixin focusOutline;\n } */\n}\n\n.Button-withTooltip {\n position: relative;\n display: inline-block;\n}\n\na.Button,\nsummary.Button {\n display: inline-flex;\n\n &:hover {\n text-decoration: none;\n }\n}\n\n/* wrap grid content to allow trailingAction to lock-right */\n.Button-content {\n flex: 1 0 auto;\n display: grid;\n grid-template-areas: 'leadingVisual text trailingVisual';\n grid-template-columns: min-content minmax(0, auto) min-content;\n align-items: center;\n place-content: center;\n /* padding-bottom: 1px; optical alignment for firefox */\n\n & > :not(:last-child) {\n margin-right: var(--primer-control-medium-gap, 8px);\n }\n}\n\n/* center child elements for fullWidth */\n.Button-content--alignStart {\n justify-content: start;\n}\n\n/* button child elements */\n\n/* align svg */\n.Button-visual {\n display: flex;\n pointer-events: none; /* allow click handler to work, avoiding visuals */\n}\n\n.Button-label {\n grid-area: text;\n white-space: nowrap;\n line-height: var(--primer-text-body-lineHeight-medium, calc(20/14));\n}\n\n.Button-leadingVisual {\n grid-area: leadingVisual;\n}\n\n.Button-trailingVisual {\n grid-area: trailingVisual;\n}\n\n.Button-trailingAction {\n margin-right: calc(var(--base-size-4, 4px) * -1);\n}\n\n/* sizes */\n\n.Button--small {\n font-size: var(--primer-text-body-size-small, 12px);\n height: var(--primer-control-small-size, 28px);\n padding: 0 var(--primer-control-small-paddingInline-normal, 12px);\n gap: var(--primer-control-small-gap, 4px);\n\n .Button-label {\n line-height: var(--primer-text-body-lineHeight-small, calc(20/12));\n }\n\n .Button-content {\n & > :not(:last-child) {\n margin-right: var(--primer-control-small-gap, 4px);\n }\n }\n}\n\n.Button--large {\n height: var(--primer-control-large-size, 40px);\n padding: 0 var(--primer-control-large-paddingInline-normal, 12px);\n gap: var(--primer-control-large-gap, 8px);\n\n .Button-label {\n line-height: var(--primer-text-body-lineHeight-large, calc(48/32));\n }\n\n .Button-content {\n & > :not(:last-child) {\n margin-right: var(--primer-control-large-gap, 8px);\n }\n }\n}\n\n.Button--fullWidth {\n width: 100%;\n}\n\n/* variants */\n\n/* primary */\n.Button--primary {\n color: var(--color-btn-primary-text);\n fill: var(--color-btn-primary-icon);\n background-color: var(--color-btn-primary-bg);\n border-color: var(--color-btn-primary-border);\n box-shadow: var(--color-btn-primary-shadow), var(--color-btn-primary-inset-shadow);\n\n &:hover {\n background-color: var(--color-btn-primary-hover-bg);\n border-color: var(--color-btn-primary-hover-border);\n }\n\n /* fallback :focus state */\n &:focus {\n @mixin focusOutlineOnEmphasis;\n\n /* remove fallback :focus if :focus-visible is supported */\n &:not(:focus-visible) {\n outline: solid 1px transparent;\n box-shadow: none;\n }\n }\n\n /* default focus state */\n &:focus-visible {\n @mixin focusOutlineOnEmphasis;\n }\n\n &:active,\n &[aria-pressed='true'],\n &.Button--pressed {\n background-color: var(--color-btn-primary-selected-bg);\n box-shadow: var(--color-btn-primary-selected-shadow);\n }\n\n &:disabled,\n &.Button--disabled,\n &[aria-disabled='true'] {\n color: var(--color-btn-primary-disabled-text);\n background-color: var(--color-btn-primary-disabled-bg);\n border-color: var(--color-btn-primary-disabled-border);\n fill: var(--color-btn-primary-disabled-text);\n }\n}\n\n/* default (secondary) */\n.Button--secondary {\n color: var(--color-btn-text);\n fill: var(--color-fg-muted); /* help this */\n background-color: var(--color-btn-bg);\n border-color: var(--color-btn-border);\n box-shadow: var(--color-btn-shadow), var(--color-btn-inset-shadow);\n\n &:hover {\n background-color: var(--color-btn-hover-bg);\n border-color: var(--color-btn-hover-border);\n }\n\n &:active,\n &.Button--active {\n background-color: var(--color-btn-active-bg);\n border-color: var(--color-btn-active-border);\n }\n\n &[aria-pressed='true'],\n &.Button--pressed {\n background-color: var(--color-btn-selected-bg);\n box-shadow: var(--color-primer-shadow-inset);\n }\n\n &:disabled,\n &.Button--disabled,\n &[aria-disabled='true'] {\n color: var(--color-primer-fg-disabled);\n background-color: var(--color-btn-bg);\n border-color: var(--color-btn-border);\n fill: var(--color-primer-fg-disabled);\n }\n}\n\n/* link color without svg */\n.Button--invisible {\n color: var(--color-fg-default);\n fill: var(--color-fg-default);\n border: none;\n\n &:hover {\n background-color: var(--color-action-list-item-default-hover-bg);\n }\n\n &[aria-pressed='true'],\n &:active,\n &.Button--active,\n &.Button--pressed {\n background-color: var(--color-action-list-item-default-active-bg);\n /* box-shadow: var(--color-primer-shadow-inset); */\n }\n\n &:disabled,\n &.Button--disabled,\n &[aria-disabled='true'] {\n color: var(--color-primer-fg-disabled);\n background-color: var(--color-btn-bg);\n border-color: var(--color-btn-border);\n fill: var(--color-primer-fg-disabled);\n }\n\n /* if visual is present, muted label color */\n .Button-label:not(:only-child) {\n color: var(--color-btn-text);\n }\n\n /* if trailingAction is present, muted label color */\n .Button-content:not(:only-child) {\n .Button-label {\n color: var(--color-btn-text);\n }\n }\n}\n\n/* danger */\n.Button--danger {\n color: var(--color-btn-danger-text);\n fill: var(--color-btn-danger-icon);\n background-color: var(--color-btn-bg);\n border-color: var(--color-btn-border);\n box-shadow: var(--color-btn-shadow), var(--color-btn-inset-shadow);\n\n &:hover {\n color: var(--color-btn-danger-hover-text);\n fill: var(--color-btn-danger-hover-text);\n background-color: var(--color-btn-danger-hover-bg);\n border-color: var(--color-btn-danger-hover-border);\n box-shadow: var(--color-btn-danger-hover-shadow), var(--color-btn-danger-hover-inset-shadow);\n }\n\n &:active,\n &[aria-pressed='true'],\n &.Button--pressed {\n color: var(--color-btn-danger-selected-text);\n fill: var(--color-btn-danger-selected-text);\n background-color: var(--color-btn-danger-selected-bg);\n border-color: var(--color-btn-danger-selected-border);\n box-shadow: var(--color-btn-danger-selected-shadow);\n }\n\n &:disabled,\n &.disabled,\n &[aria-disabled='true'] {\n color: var(--color-btn-danger-disabled-text);\n fill: var(--color-btn-danger-disabled-text);\n background-color: var(--color-btn-danger-disabled-bg);\n border-color: var(--color-btn-border);\n }\n}\n\n.Button--iconOnly {\n display: grid;\n place-content: center;\n padding: unset;\n width: var(--primer-control-medium-size, 32px);\n\n &.Button--small {\n width: var(--primer-control-small-size, 28px);\n }\n\n &.Button--large {\n width: var(--primer-control-large-size, 40px);\n }\n}\n",null,"/* outline with fg box-shadow for buttons */\n@define-mixin focusOutlineOnEmphasis $outlineOffset: -2px, $outlineColor: var(--color-accent-fg) {\n outline: 2px solid $outlineColor;\n outline-offset: $outlineOffset;\n box-shadow: inset 0 0 0 3px var(--color-fg-on-emphasis);\n}\n"]}
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Alpha
5
+ class Dialog
6
+ # A `Dialog::Body` is a compositional component, used to render the
7
+ # Body of a dialog. See <%= link_to_component(Primer::Alpha::Dialog) %>.
8
+ class Body < Primer::Component
9
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
10
+ def initialize(**system_arguments)
11
+ @system_arguments = deny_tag_argument(**system_arguments)
12
+ @system_arguments[:tag] = :div
13
+ @system_arguments[:classes] = class_names(
14
+ "Overlay-body",
15
+ system_arguments[:classes]
16
+ )
17
+ end
18
+
19
+ def call
20
+ render(Primer::BaseComponent.new(**@system_arguments)) { content }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Alpha
5
+ class Dialog
6
+ # A `Dialog::Footer` is a compositional component, used to render the
7
+ # Footer of a dialog. See <%= link_to_component(Primer::Alpha::Dialog) %>.
8
+ class Footer < Primer::Component
9
+ # @param show_divider [Boolean] Show a divider between the footer and body.
10
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
11
+ def initialize(
12
+ show_divider: false,
13
+ **system_arguments
14
+ )
15
+ @system_arguments = deny_tag_argument(**system_arguments)
16
+ @system_arguments[:tag] = :div
17
+ @system_arguments[:classes] = class_names(
18
+ "Overlay-footer",
19
+ "Overlay-footer--alignEnd",
20
+ { "Overlay-footer--divided": show_divider },
21
+ system_arguments[:classes]
22
+ )
23
+ end
24
+
25
+ def call
26
+ render(Primer::BaseComponent.new(**@system_arguments)) { content }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ <%= render Primer::BaseComponent.new(**@system_arguments) do %>
2
+ <div class="Overlay-headerContentWrap">
3
+ <div class="Overlay-titleWrap">
4
+ <h1 class="Overlay-title <% if @visually_hide_title || content.present? %>sr-only<% end %>"><%= @title %></h1>
5
+ <% if content.present? %>
6
+ <%= content %>
7
+ <% elsif @subtitle.present? %>
8
+ <h2 id="<%= @id %>-description" class="Overlay-description"><%= @subtitle %></h2>
9
+ <% end %>
10
+ </div>
11
+ <div class="Overlay-actionWrap">
12
+ <%= render Primer::Beta::CloseButton.new(classes: "Overlay-closeButton", "data-close-dialog-id": @id) %>
13
+ </div>
14
+ </div>
15
+ <% end %>
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Alpha
5
+ class Dialog
6
+ # A `Dialog::Header` is a compositional component, used to render the
7
+ # Header of a dialog. See <%= link_to_component(Primer::Alpha::Dialog) %>.
8
+ class Header < Primer::Component
9
+ # @param title [String] Describes the content of the dialog.
10
+ # @param subtitle [String] Provides dditional context for the dialog, also setting the `aria-describedby` attribute.
11
+ # @param show_divider [Boolean] Show a divider between the header and body.
12
+ # @param visually_hide_title [Boolean] Visually hide the `title` while maintaining a label for assistive technologies.
13
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
14
+ def initialize(
15
+ id:,
16
+ title:,
17
+ subtitle: nil,
18
+ show_divider: false,
19
+ visually_hide_title: false,
20
+ **system_arguments
21
+ )
22
+ @id = id
23
+ @title = title
24
+ @subtitle = subtitle
25
+ @visually_hide_title = visually_hide_title
26
+ @system_arguments = deny_tag_argument(**system_arguments)
27
+ @system_arguments[:tag] = :header
28
+ @system_arguments[:classes] = class_names(
29
+ "Overlay-header",
30
+ { "Overlay-header--divided": show_divider },
31
+ system_arguments[:classes]
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,12 @@
1
+ <%= show_button %>
2
+ <div class="Overlay--hidden <%= @backdrop_classes %>" data-modal-dialog-overlay>
3
+ <%= render Primer::BaseComponent.new(**@system_arguments) do %>
4
+ <%= header %>
5
+ <% if content.present? %>
6
+ <%= content %>
7
+ <% else %>
8
+ <%= body %>
9
+ <%= footer %>
10
+ <% end %>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Alpha
5
+ # A `Dialog` is used to remove the user from the main application flow,
6
+ # to confirm actions, ask for disambiguation or to present small forms.
7
+ #
8
+ # @accessibility
9
+ # - **Dialog Accessible Name**: A dialog should have an accessible name,
10
+ # so screen readers are aware of the purpose of the dialog when it opens.
11
+ # Give an accessible name setting `:title`. The accessible name will be
12
+ # used as the main heading inside the dialog.
13
+ # - **Dialog unique id**: A dialog should be unique. Give a unique id
14
+ # setting `:dialog_id`. If no `:dialog_id` is given, a default randomize
15
+ # hex id is generated.
16
+ #
17
+ # The combination of both `:title` and `:dialog_id` establishes an
18
+ # `aria-labelledby` relationship between the title and the unique id of
19
+ # the dialog.
20
+ class Dialog < Primer::Component
21
+ DEFAULT_SIZE = :medium
22
+ SIZE_MAPPINGS = {
23
+ :small => "Overlay--size-small-portrait",
24
+ :medium_portrait => "Overlay--size-medium-portrait",
25
+ DEFAULT_SIZE => "Overlay--size-medium",
26
+ :large => "Overlay--size-large",
27
+ :xlarge => "Overlay--size-xlarge"
28
+ }.freeze
29
+ SIZE_OPTIONS = SIZE_MAPPINGS.keys
30
+
31
+ DEFAULT_POSITION = :center
32
+ POSITION_MAPPINGS = {
33
+ DEFAULT_POSITION => "Overlay-backdrop--center",
34
+ :left => "Overlay-backdrop--side Overlay-backdrop--placement-left",
35
+ :right => "Overlay-backdrop--side Overlay-backdrop--placement-right"
36
+ }.freeze
37
+ POSITION_OPTIONS = POSITION_MAPPINGS.keys
38
+
39
+ DEFAULT_POSITION_NARROW = :inherit
40
+ POSITION_NARROW_MAPPINGS = {
41
+ DEFAULT_POSITION_NARROW => "",
42
+ :bottom => "Overlay-backdrop--side-whenNarrow Overlay-backdrop--placement-bottom-whenNarrow",
43
+ :fullscreen => "Overlay-backdrop--full-whenNarrow",
44
+ :left => "Overlay-backdrop--side-whenNarrow Overlay-backdrop--placement-left-whenNarrow",
45
+ :right => "Overlay-backdrop--side-whenNarrow Overlay-backdrop--placement-right-whenNarrow"
46
+ }.freeze
47
+ POSITION_NARROW_OPTIONS = POSITION_NARROW_MAPPINGS.keys
48
+
49
+ # Optional button to open the dialog.
50
+ #
51
+ # @param system_arguments [Hash] The same arguments as <%= link_to_component(Primer::ButtonComponent) %>.
52
+ renders_one :show_button, lambda { |**system_arguments|
53
+ system_arguments[:classes] = class_names(
54
+ system_arguments[:classes]
55
+ )
56
+ system_arguments[:id] = "dialog-show-#{@system_arguments[:id]}"
57
+ system_arguments["data-show-dialog-id"] = @system_arguments[:id]
58
+ system_arguments[:data] = (system_arguments[:data] || {}).merge({ "show-dialog-id": @system_arguments[:id] })
59
+ Primer::ButtonComponent.new(**system_arguments)
60
+ }
61
+
62
+ # Header content.
63
+ #
64
+ # @param show_divider [Boolean] Show a divider between the header and body.
65
+ # @param visually_hide_title [Boolean] Visually hide the `title` while maintaining a label for assistive technologies.
66
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
67
+ renders_one :header, lambda { |show_divider: false, visually_hide_title: @visually_hide_title, **system_arguments|
68
+ Primer::Alpha::Dialog::Header.new(
69
+ id: @id,
70
+ title: @title,
71
+ subtitle: @subtitle,
72
+ show_divider: show_divider,
73
+ visually_hide_title: visually_hide_title,
74
+ **system_arguments
75
+ )
76
+ }
77
+
78
+ # Required body content.
79
+ #
80
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
81
+ renders_one :body, "Body"
82
+
83
+ # Footer content.
84
+ #
85
+ # @param show_divider [Boolean] Show a divider between the footer and body.
86
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
87
+ renders_one :footer, "Footer"
88
+
89
+ # @example Dialog with Cancel and Submit buttons
90
+ # @description
91
+ # An ID is provided which enables wiring of the open and close buttons to the dialog.
92
+ # @code
93
+ # <%= render(Primer::Alpha::Dialog.new(
94
+ # title: "Dialog Example",
95
+ # id: "my-dialog",
96
+ # )) do |d| %>
97
+ # <% d.with_show_button { "Show Dialog" } %>
98
+ # <% d.with_body do %>
99
+ # <p>Some content</p>
100
+ # <% end %>
101
+ # <% d.footer do %>
102
+ # <%= render(Primer::ButtonComponent.new(data: { "close-dialog-id": "my-dialog" })) { "Cancel" } %>
103
+ # <%= render(Primer::ButtonComponent.new(scheme: :primary)) { "Submit" } %>
104
+ # <% end %>
105
+ # <% end %>
106
+ # @param id [String] The id of the dialog.
107
+ # @param title [String] Describes the content of the dialog.
108
+ # @param subtitle [String] Provides dditional context for the dialog, also setting the `aria-describedby` attribute.
109
+ # @param size [Symbol] The size of the dialog. <%= one_of(Primer::Alpha::Dialog::SIZE_OPTIONS) %>
110
+ # @param position [Symbol] The size of the dialog. <%= one_of(Primer::Alpha::Dialog::POSITION_OPTIONS) %>
111
+ # @param position_narrow [Symbol] The size of the dialog. <%= one_of(Primer::Alpha::Dialog::POSITION_NARROW_OPTIONS) %>
112
+ # @param visually_hide_title [Boolean] If true will hide the heading title, while still making it available to Screen Readers.
113
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
114
+ def initialize(
115
+ title:,
116
+ subtitle: nil,
117
+ size: DEFAULT_SIZE,
118
+ position: DEFAULT_POSITION,
119
+ position_narrow: DEFAULT_POSITION_NARROW,
120
+ visually_hide_title: false,
121
+ id: "dialog-#{(36**3 + rand(36**4)).to_s(36)}",
122
+ **system_arguments
123
+ )
124
+ @system_arguments = deny_tag_argument(**system_arguments)
125
+
126
+ @system_arguments[:tag] = "modal-dialog"
127
+ @system_arguments[:role] = "dialog"
128
+ @system_arguments[:id] = id.to_s
129
+ @system_arguments[:aria] = { modal: true }
130
+ @system_arguments[:classes] = class_names(
131
+ "Overlay",
132
+ "Overlay-whenNarrow",
133
+ SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)],
134
+ "Overlay--motion-scaleFade",
135
+ system_arguments[:classes]
136
+ )
137
+ @backdrop_classes = class_names(
138
+ POSITION_MAPPINGS[fetch_or_fallback(POSITION_OPTIONS, position, DEFAULT_POSITION)],
139
+ POSITION_NARROW_MAPPINGS[fetch_or_fallback(POSITION_NARROW_MAPPINGS, position_narrow, DEFAULT_POSITION_NARROW)]
140
+ )
141
+
142
+ @id = id.to_s
143
+ @title = title
144
+ @position = position
145
+ @position_narrow = position_narrow
146
+ @visually_hide_title = visually_hide_title
147
+
148
+ @subtitle = subtitle
149
+
150
+ @system_arguments[:aria] ||= {}
151
+ @system_arguments[:aria][:describedby] ||= "#{@id}-description"
152
+ end
153
+
154
+ def before_render
155
+ with_header unless header?
156
+ with_body unless body?
157
+ end
158
+ end
159
+ end
160
+ end
@@ -6,7 +6,7 @@ export declare class ModalDialogElement extends HTMLElement {
6
6
  connectedCallback(): void;
7
7
  disconnectedCallback(): void;
8
8
  show(): void;
9
- close(closed?: boolean): void;
9
+ close(closedNotCancelled?: boolean): void;
10
10
  }
11
11
  declare global {
12
12
  interface Window {
@@ -92,7 +92,6 @@ export class ModalDialogElement extends HTMLElement {
92
92
  }
93
93
  dialogId = button.getAttribute('data-show-dialog-id');
94
94
  if (dialogId === this.id) {
95
- //TODO: see if I can remove this
96
95
  event.stopPropagation();
97
96
  __classPrivateFieldSet(this, _ModalDialogElement_openButton, button, "f");
98
97
  this.show();
@@ -107,10 +106,10 @@ export class ModalDialogElement extends HTMLElement {
107
106
  show() {
108
107
  this.open = true;
109
108
  }
110
- close(closed = false) {
109
+ close(closedNotCancelled = false) {
111
110
  if (this.open === false)
112
111
  return;
113
- const eventType = closed ? 'close' : 'cancel';
112
+ const eventType = closedNotCancelled ? 'close' : 'cancel';
114
113
  const dialogEvent = new Event(eventType);
115
114
  this.dispatchEvent(dialogEvent);
116
115
  this.open = false;
@@ -0,0 +1,148 @@
1
+ import {focusTrap} from '@primer/behaviors'
2
+ import {getFocusableChild} from '@primer/behaviors/utils'
3
+
4
+ function focusIfNeeded(elem?: HTMLElement) {
5
+ if (document.activeElement !== elem) {
6
+ elem?.focus()
7
+ }
8
+ }
9
+
10
+ export class ModalDialogElement extends HTMLElement {
11
+ //TODO: Do we remove the abortController from focusTrap?
12
+ #focusAbortController = new AbortController()
13
+ #abortController: AbortController | null = null
14
+ #openButton: HTMLButtonElement | undefined
15
+ #shouldTryLoadingFragment = true
16
+
17
+ get open() {
18
+ return this.hasAttribute('open')
19
+ }
20
+ set open(value: boolean) {
21
+ if (value) {
22
+ if (this.open) return
23
+ this.setAttribute('open', '')
24
+ this.#overlayBackdrop?.classList.remove('Overlay--hidden')
25
+ document.body.style.overflow = 'hidden'
26
+ if (this.#focusAbortController.signal.aborted) {
27
+ this.#focusAbortController = new AbortController()
28
+ }
29
+ focusTrap(this, undefined, this.#focusAbortController.signal)
30
+ } else {
31
+ if (!this.open) return
32
+ this.removeAttribute('open')
33
+ this.#overlayBackdrop?.classList.add('Overlay--hidden')
34
+ document.body.style.overflow = 'initial'
35
+ this.#focusAbortController.abort()
36
+ // if #openButton is a child of a menu, we need to focus a suitable child of the menu
37
+ // element since it is expected for the menu to close on click
38
+ const menu = this.#openButton?.closest('details') || this.#openButton?.closest('action-menu')
39
+ if (menu) {
40
+ focusIfNeeded(getFocusableChild(menu))
41
+ } else {
42
+ focusIfNeeded(this.#openButton)
43
+ }
44
+ this.#openButton = undefined
45
+ }
46
+ }
47
+
48
+ get #overlayBackdrop(): HTMLElement | null {
49
+ if (this.parentElement?.hasAttribute('data-modal-dialog-overlay')) {
50
+ return this.parentElement
51
+ }
52
+
53
+ return null
54
+ }
55
+
56
+ get showButtons(): NodeList {
57
+ // Dialogs may also be opened from any arbitrary button with a matching show-dialog-id data attribute
58
+ return document.querySelectorAll(`button[data-show-dialog-id='${this.id}']`)
59
+ }
60
+
61
+ connectedCallback(): void {
62
+ if (!this.hasAttribute('role')) this.setAttribute('role', 'dialog')
63
+
64
+ const {signal} = (this.#abortController = new AbortController())
65
+
66
+ this.ownerDocument.addEventListener(
67
+ 'click',
68
+ event => {
69
+ const target = event.target as HTMLElement
70
+ const clickOutsideDialog = target.closest(this.tagName) !== this
71
+ const button = target?.closest('button')
72
+ // go over this logic:
73
+ if (!button) {
74
+ if (clickOutsideDialog) {
75
+ // This click is outside the dialog
76
+ this.close()
77
+ }
78
+ return
79
+ }
80
+
81
+ let dialogId = button.getAttribute('data-close-dialog-id')
82
+ if (dialogId === this.id) {
83
+ this.close()
84
+ }
85
+
86
+ dialogId = button.getAttribute('data-submit-dialog-id')
87
+ if (dialogId === this.id) {
88
+ this.close(true)
89
+ }
90
+
91
+ dialogId = button.getAttribute('data-show-dialog-id')
92
+ if (dialogId === this.id) {
93
+ event.stopPropagation()
94
+ this.#openButton = button
95
+ this.show()
96
+ }
97
+ },
98
+ {signal}
99
+ )
100
+
101
+ this.addEventListener('keydown', e => this.#keydown(e))
102
+ }
103
+
104
+ disconnectedCallback(): void {
105
+ this.#abortController?.abort()
106
+ }
107
+
108
+ show() {
109
+ this.open = true
110
+ }
111
+
112
+ close(closedNotCancelled = false) {
113
+ if (this.open === false) return
114
+ const eventType = closedNotCancelled ? 'close' : 'cancel'
115
+ const dialogEvent = new Event(eventType)
116
+ this.dispatchEvent(dialogEvent)
117
+ this.open = false
118
+ }
119
+
120
+ #keydown(event: Event) {
121
+ if (!(event instanceof KeyboardEvent)) return
122
+ if (event.isComposing) return
123
+
124
+ switch (event.key) {
125
+ case 'Escape':
126
+ if (this.open) {
127
+ this.close()
128
+ event.preventDefault()
129
+ event.stopPropagation()
130
+ }
131
+ break
132
+ }
133
+ }
134
+ }
135
+
136
+ declare global {
137
+ interface Window {
138
+ ModalDialogElement: typeof ModalDialogElement
139
+ }
140
+ interface HTMLElementTagNameMap {
141
+ 'modal-dialog': ModalDialogElement
142
+ }
143
+ }
144
+
145
+ if (!window.customElements.get('modal-dialog')) {
146
+ window.ModalDialogElement = ModalDialogElement
147
+ window.customElements.define('modal-dialog', ModalDialogElement)
148
+ }
@@ -99,6 +99,8 @@ let ToggleSwitchElement = class ToggleSwitchElement extends HTMLElement {
99
99
  }
100
100
  body.append('value', this.isOn() ? '1' : '0');
101
101
  try {
102
+ if (!this.src)
103
+ throw new Error('invalid src');
102
104
  const response = await fetch(this.src, {
103
105
  credentials: 'same-origin',
104
106
  method: 'POST',
@@ -123,7 +123,8 @@ export class ToggleSwitchElement extends HTMLElement {
123
123
  body.append('value', this.isOn() ? '1' : '0')
124
124
 
125
125
  try {
126
- const response = await fetch(this.src!, {
126
+ if (!this.src) throw new Error('invalid src')
127
+ const response = await fetch(this.src, {
127
128
  credentials: 'same-origin',
128
129
  method: 'POST',
129
130
  body
@@ -1,4 +1,3 @@
1
- // eslint-disable-next-line prettier/prettier
2
1
  import type {AnchorAlignment, AnchorSide} from '@primer/behaviors'
3
2
  import {getAnchoredPosition} from '@primer/behaviors'
4
3
 
@@ -0,0 +1,23 @@
1
+ <%= render Primer::ConditionalWrapper.new(condition: tooltip.present?, tag: :div, classes: "Button-withTooltip") do -%>
2
+ <%= render Primer::Beta::BaseButton.new(**@system_arguments) do -%>
3
+ <span class="<%= @align_content_classes %>">
4
+ <% if leading_visual %>
5
+ <span class="Button-visual Button-leadingVisual">
6
+ <%= leading_visual %>
7
+ </span>
8
+ <% end %>
9
+ <span class="Button-label"><%= trimmed_content %></span>
10
+ <% if trailing_visual %>
11
+ <span class="Button-visual Button-trailingVisual">
12
+ <%= trailing_visual %>
13
+ </span>
14
+ <% end %>
15
+ </span>
16
+ <% if trailing_action %>
17
+ <span class="Button-visual Button-trailingAction">
18
+ <%= trailing_action %>
19
+ </span>
20
+ <% end %>
21
+ <%= tooltip %>
22
+ <% end -%>
23
+ <% end -%>