message: Update size description

diff:
@@ -22,7 +22,7 @@ module Primer
# @example 80|Large
# <%= render(Primer::SpinnerComponent.new(size: :large)) %>
#
- # @param size [Symbol] Sets the size of the spinner
+ # @param size [Symbol] One of <%= Primer::SpinnerComponent::SIZE_OPTIONS.map { |k| "`:#{k}`" }.to_sentence(last_word_connector: ', or ') %>
def initialize(size: DEFAULT_SIZE, **kwargs)
@kwargs = kwargs
@kwargs[:tag] = :svg

diff:
@@ -34,4 +34,4 @@ Use Primer::SpinnerComponent to let users know that content is being loaded.
| Name | Type | Default | Description |
| :- | :- | :-: | :- |
-| size | Symbol | DEFAULT_SIZE | Sets the size of the spinner |
+| size | Symbol | DEFAULT_SIZE | One of `:small`, `:medium`, or `:large` |

original_message: Update size description


message: add storybook for counter component

change_type: ADD

diff:
+class Primer::CounterComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:counter) do
+ controls do
+ count 0
+ select(:scheme, Primer::CounterComponent::SCHEME_MAPPINGS.keys, :gray)
+ limit 5000
+ hide_if_zero false
+ round false
+ end
+ end
+end

license: MIT License

original_message: add storybook for counter component


message: decrement storybook counter linter

change_type: MODIFY

diff:
@@ -82,9 +82,9 @@ class PrimerComponentTest < Minitest::Test
def test_components_storybook_count
+ # Should be deprecated each time a new storybook is added to a component
# Should be incremented if a new view component is added without a storybook
- # Should be incremented if a new view component is added without a storybook
- expected_missing_stories = 12
+ expected_missing_stories = 11
expected_components_count = COMPONENTS_WITH_ARGS.length

original_message: decrement storybook counter linter


message: add story for progres bar component

diff:
+# frozen_string_literal: true
+
+class Primer::ProgressBarComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:progress_bar) do
+ controls do
+ select(:size, Primer::ProgressBarComponent::SIZE_MAPPINGS.keys, :small)
+ end
+
+ content do |component|
+ component.slot(:item, bg: :green, percentage: 10)
+ end
+ end
+end

original_message: add story for progres bar component


message: decrement storybook counter by 1

diff:
@@ -83,9 +83,9 @@ class PrimerComponentTest < Minitest::Test
def test_components_storybook_count
+ # Should be deprecated each time a new storybook is added to a component
# Should be incremented if a new view component is added without a storybook
- # Should be incremented if a new view component is added without a storybook
- expected_missing_stories = 12
+ expected_missing_stories = 11
expected_components_count = COMPONENTS_WITH_ARGS.length

original_message: decrement storybook counter by 1


message: add story for breadcrumb component

diff:
+# frozen_string_literal: true
+
+class Primer::BreadcrumbComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:breadcrumb) do
+ content do |component|
+ component.slot(:item, selected: false) { "Breadcrumb Item one" }
+ component.slot(:item, href: "https://github.com/") { "Breadcrumb Item two" }
+ end
+ end
+end

original_message: add story for breadcrumb component


message: decrement storybook linter

diff:
@@ -83,9 +83,9 @@ class PrimerComponentTest < Minitest::Test
def test_components_storybook_count
+ # Should be Should be deprecated each time a new storybook is added to a component
# Should be incremented if a new view component is added without a storybook
- # Should be incremented if a new view component is added without a storybook
- expected_missing_stories = 12
+ expected_missing_stories = 11
expected_components_count = COMPONENTS_WITH_ARGS.length

original_message: decrement storybook linter


message: add storybook for box component

diff:
+# frozen_string_literal: true
+
+class Primer::BoxComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:box) do
+ content do
+ "This is a div"
+ end
+ end
+end

original_message: add storybook for box component


message: add story for heading component

diff:
+# frozen_string_literal: true
+
+class Primer::HeadingComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:heading) do
+ content do
+ "This is a heading!"
+ end
+ end
+end

original_message: add story for heading component


message: decrement storybook counter

diff:
@@ -84,7 +84,8 @@ class PrimerComponentTest < Minitest::Test
def test_components_storybook_count
# Should be deprecated each time a new storybook is added to a component
# Should be incremented if a new view component is added without a storybook
- expected_missing_stories = 8
+ expected_missing_stories = 7
+
expected_components_count = COMPONENTS_WITH_ARGS.length
storybook_count = Dir[Rails.root.join("../stories/primer/**/*.rb")].length

original_message: decrement storybook counter


message: add story for underline nav component

diff:
+# frozen_string_literal: true
+
+class Primer::UnderlineNavComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:underline_nav) do
+ controls do
+ select(:align, Primer::UnderlineNavComponent::ALIGN_OPTIONS, :left)
+ end
+
+ content do |component|
+ component.with(:body) { "body" }
+ end
+ end
+end

diff:
@@ -95,7 +95,6 @@ class PrimerComponentTest < Minitest::Test
"subhead_component",
"flex_item_component",
"dropdown_menu_component",
- "underline_nav_component",
"base_component",
"flex_component",
]

original_message: add story for underline nav component


message: add storybook story to text component

diff:
+# frozen_string_literal: true
+
+class Primer::TextComponentStories < ViewComponent::Storybook::Stories
+ layout layout "storybook_preview"
+
+ story(:text) do
+ content do
+ "text"
+ end
+ end
+end

diff:
@@ -91,7 +91,6 @@ class PrimerComponentTest < Minitest::Test
"component", # No story needed
"view_components", # No story needed
"slot", "component", # No story needed
- "text_component",
"subhead_component",
"flex_item_component",
"dropdown_menu_component",

original_message: add storybook story to text component


message: add storybook story for subhead component

diff:
+# frozen_string_literal: true
+
+class Primer::SubheadComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:subhead) do
+ controls do
+ spacious false
+ hide_border false
+ end
+
+ content do |component|
+ component.slot(:heading, danger: false, tag: :h1) { "My Profile Heading" }
+ component.slot(:description, tag: :span) { "This is a description of my profile. I live on planet Earth. Sometimes." }
+ component.slot(:actions, classes: "btn btn-sm") { "Link to personal website" }
+ end
+ end
+
+ story(:danger_heading) do
+ content do |component|
+ component.slot(:heading, danger: true) { "Danger Heading" }
+ end
+ end
+end

diff:
@@ -92,7 +92,6 @@ class PrimerComponentTest < Minitest::Test
"view_components", # No story needed
"slot", "component", # No story needed
"text_component",
- "subhead_component",
"flex_item_component",
"dropdown_menu_component",
"underline_nav_component",


message: Use Note component

diff:
@@ -7,7 +7,7 @@ export default HeroLayout
Primer ViewComponents is an implementation of the Primer Design System, using [ViewComponent](https://github.com/github/view_component).
-_Note: This library is in pre-release development and should not be considered stable._
+<Note variant="warning">This library is in pre-release development and should not be considered stable.</Note>
## Usage

original_message: Use Note component


message: Update octicons_helper runtime dependency version

diff:
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
spec.files = Dir["CHANGELOG.md", "LICENSE.txt", "README.md", "lib/**/*", "app/**/*"]
spec.require_paths = ["lib"]
- spec.add_runtime_dependency "octicons_helper", [">= 9.0.0", "< 12.0.0"]
+ spec.add_runtime_dependency "octicons_helper", [">= 9.0.0", "< 13.0.0"]
spec.add_runtime_dependency "rails", [">= 5.0.0", "< 7.0"]
spec.add_runtime_dependency "view_component", [">= 2.0.0", "< 3.0"]
spec.add_development_dependency "allocation_tracer", "~> 0.6.3"


message: docs(adr): add 001 Record Architecture Decisions

diff:
+# 1. Record architecture decisions
+
+Date: 2021-02-19
+
+## Status
+
+Proposed
+
+## Context
+
+We need to record the architectural decisions made on this project.
+
+## Decision
+
+We will use Architecture Decision Records, as [described by Michael Nygard](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions).
+
+## Consequences
+
+See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).


message: docs(adr): add Developing and Publishing ClientSide Behaviours

diff:
+# 000X. Developing and Publishing ClientSide Behaviours
+
+Date: 2021-02-19
+
+## Status
+
+Proposed
+
+## Context
+
+We want to ship client side behavioural elements with Primer View Components to increase the fidelity of our components.
+
+Doing so requires us to align to a set of constraints for the Browser ecosystem, as well as the ecosystems that consumers of Primer View Components have:
+
+ - We need to produce a JavaScript based artefact, as this is the language browsers use for client side behaviours.
+ - The artefact should be easily consumable by existing tool chains of our consumers, which may or may not be the Rails Asset Pipeline.
+ - The setup process should be uncomplicated and familiar to consumers; by using existing Rails paradigms, or JavaScript ecosystem paradigms or tools such as npm.
+ - Client side behaviours should be well tested and offer the same level of quality the existing non-behavioural components do.
+
+## Decision
+
+### TypeScript
+
+We will write behaviours in TypeScript, which is a compile-to-JavaScript language that offers stronger type guarantees leading to a higher quality level of code. TypeScript is already being used for [`primer/components`](https://github.com/primer/components) which is also a motivating factor.
+
+Behaviours will live in TypeScript files in the `app/components` directory next to their respective `.rb` files. By convention files will be named with the same basename as the Ruby counterpart. An `app/components/primer.ts` file will also exist which must import all other `.ts` files, acting as an index file. A single `.js` file with no external dependencies, which will live in `app/assets/javascripts/primer.js`. This single file can then be loaded through the Rails Asset pipeline, allowing for consumers to call `javascript_include_tag("primer")`.
+
+### Publishing to npm
+
+Additionally, we will produce an [npm package](https://www.npmjs.com/) which contains all the compiled down JavaScript both as individual files and as the single bundle in `app/assets/javascripts/primer.js`. This npm package will allow for consumption of the JS outside of using the traditional Rails Asset Pipeline. Publishing a side-car package like this moderately increases complexity for this library, but drastically _increases_ flexibility and decreases the burden on the library to provide flexible paths for the variety of JavaScript compilers in use today. Good precedent exists for offering sidecar client side libraries, for example the [Turbo Framework offers a sidecar package](https://github.com/hotwired/turbo-rails#installation) if not using the built in Asset Pipeline.
+
+### Testing
+
+We will test our behaviours in the Browser environment (using System tests), as this is where they will be consumed. This will require using Browser APIs to automate testing. We will use test automation libraries that automate browsers via the [Devtools Protocol](https://chromedevtools.github.io/devtools-protocol/) such as [Puppeteer](https://pptr.dev/), [Playwright](https://playwright.dev/) or [Ferrum](https://ferrum.rocks/). Using a Devtools Protocol based tool gives us the best level of flexibility in testing, while also being compatible with current browsers.
+
+### Documentation
+
+We will document which components are behavioural, so that consumers are aware they are required to load the client side bundle code for these components to work properly. We will offer a documentation page on how to use the `javascript_include_tag` helper to load the bundle using the Asset Bundle, or otherwise import the npm package within their codebases.
+
+## Consequences
+
+This library will be able to provide behaviours for higher fidelity components, using consistent methods that lean to the strengths of the Rails & JS ecosystems.
+
+Contributors of Primer View Components will need to have an understanding of TypeScript if they wish to author behavioural components.
+
+There will be additional work for our consumers if they wish to use behavioural components. Consumers will need to use the `javascript_include_tag` helper, or otherwise import the npm bundle in order for these components to work in their projects.


message: docs(adr): clarify npm package features.

diff:
@@ -27,7 +27,9 @@ Behaviours will live in TypeScript files in the `app/components` directory next
### Publishing to npm
-Additionally, we will produce an [npm package](https://www.npmjs.com/) which contains all the compiled down JavaScript both as individual files and as the single bundle in `app/assets/javascripts/primer.js`. This npm package will allow for consumption of the JS outside of using the traditional Rails Asset Pipeline. Publishing a side-car package like this moderately increases complexity for this library, but drastically _increases_ flexibility and decreases the burden on the library to provide flexible paths for the variety of JavaScript compilers in use today. Good precedent exists for offering sidecar client side libraries, for example the [Turbo Framework offers a sidecar package](https://github.com/hotwired/turbo-rails#installation) if not using the built in Asset Pipeline.
+Additionally, we will produce an [npm package](https://www.npmjs.com/) which contains all the compiled down JavaScript both as individual files and as the single bundle in `app/assets/javascripts/primer.js`. This npm package will allow for consumption of the JS outside of using the traditional Rails Asset Pipeline. Developers will be able to call `import "@primer/view_components"` within their JS, which means their JS toolchain will be able to perform necessary transformations or optimisations. This will also allow for selective component imports, for example `import "@primer/view_components/time_ago_component"` could import just the "TimeAgoComponent".
+
+Publishing a side-car package like this moderately increases complexity for this library, but drastically _increases_ flexibility and decreases the burden on the library to provide flexible paths for the variety of JavaScript compilers in use today. Good precedent exists for offering sidecar client side libraries, for example the [Turbo Framework offers a sidecar package](https://github.com/hotwired/turbo-rails#installation) if not using the built in Asset Pipeline.
### Testing


message: docs(adr): change adr number

change_type: RENAME

diff:
-# 000X. Developing and Publishing ClientSide Behaviours
+# 0250. Developing and Publishing ClientSide Behaviours
Date: 2021-02-19

original_message: docs(adr): change adr number


message: test(all): split up tests for clarity

diff:
@@ -61,28 +61,43 @@ class PrimerComponentTest < Minitest::Test
assert_equal primer_component_files_count, COMPONENTS_WITH_ARGS.length + ignored_components.count, "Primer component added. Please update this test with an entry for your new component <3"
end
- def test_primer_components_provide_a_consistent_interface
+ def test_all_components_support_system_arguments
COMPONENTS_WITH_ARGS.each do |component, args, proc|
- # component renders hash arguments
render_component(component, { my: 4 }.merge(args), proc)
assert_selector(".my-4")
+ end
+ end
- # component passes through class_names
+ def test_all_components_pass_through_classes
+ COMPONENTS_WITH_ARGS.each do |component, args, proc|
render_component(component, { classes: "foo" }.merge(args), proc)
assert_selector(".foo")
+ end
+ end
- # component supports inline styles
+ def test_all_components_support_inline_styles
+ COMPONENTS_WITH_ARGS.each do |component, args, proc|
render_component(component, { style: "width: 100%;" }.merge(args), proc)
assert_selector("[style='width: 100%;']")
+ end
+ end
- # component supports basic content_tag arguments
+ def test_all_components_support_content_tag_arguments
+ COMPONENTS_WITH_ARGS.each do |component, args, proc|
render_component(component, { hidden: true }.merge(args), proc)
assert_selector("[hidden]", visible: false)
+ end
+ end
+ def test_all_components_support_data_tag_arguments
+ COMPONENTS_WITH_ARGS.each do |component, args, proc|
render_component(component, { "data-ga-click": "Foo,bar" }.merge(args), proc)
assert_selector("[data-ga-click='Foo,bar']", visible: false)
+ end
+ end
- # Ensure all slots accept Primer system_arguments
+ def test_all_slots_support_system_arguments
+ COMPONENTS_WITH_ARGS.each do |component, args, proc|
next unless component.respond_to?(:slots) && component.slots.any?
result = render_inline(component.new(**args)) do |c|

original_message: test(all): split up tests for clarity


message: docs(adr): minor typo fixes

diff:
@@ -27,9 +27,9 @@ Behaviours will live in TypeScript files in the `app/components` directory next
### Publishing to npm
-Additionally, we will produce an [npm package](https://www.npmjs.com/) which contains all the compiled down JavaScript both as individual files and as the single bundle in `app/assets/javascripts/primer.js`. This npm package will allow for consumption of the JS outside of using the traditional Rails Asset Pipeline. Developers will be able to call `import "@primer/view_components"` within their JS, which means their JS toolchain will be able to perform necessary transformations or optimisations. This will also allow for selective component imports, for example `import "@primer/view_components/time_ago_component"` could import just the "TimeAgoComponent".
+Additionally, we will produce an [npm package](https://www.npmjs.com/) which contains all the compiled JavaScript both as individual files and as the single bundle in `app/assets/javascripts/primer.js`. This npm package will allow for consumption of the JS outside of using the traditional Rails Asset Pipeline. Developers will be able to call `import "@primer/view_components"` within their JS, which means their JS toolchain will be able to perform necessary transformations or optimisations. This will also allow for selective component imports, for example `import "@primer/view_components/time_ago_component"` could import just `TimeAgoComponent`.
-Publishing a side-car package like this moderately increases complexity for this library, but drastically _increases_ flexibility and decreases the burden on the library to provide flexible paths for the variety of JavaScript compilers in use today. Good precedent exists for offering sidecar client side libraries, for example the [Turbo Framework offers a sidecar package](https://github.com/hotwired/turbo-rails#installation) if not using the built in Asset Pipeline.
+Publishing a sidecar package like this moderately increases _complexity_ for this library, but drastically increases _flexibility_ and decreases the burden on the library to provide flexible paths for the variety of JavaScript compilers in use today. Good precedent exists for offering sidecar client side libraries, for example the [Turbo Framework offers a sidecar package](https://github.com/hotwired/turbo-rails#installation) if not using the built in Asset Pipeline.
### Testing

original_message:
docs(adr): minor typo fixes

Co-authored-by: Joel Hawksley <joelhawksley@github.com>


message: feat(TimeAgo): add TimeAgoComponent

diff:
* **Breaking change**: Upgrade `StateComponent` to support functional colors. This change requires using [@primer/css-next](https://www.npmjs.com/package/@primer/css-next). The required changes will be upstreamed to @primer/css at a later date.
+* Add `TimeAgo` component.
+
+ *Keith Cirkel*
+
## 0.0.21
* **Breaking change**: Upgrade `FlashComponent` to use Slots V2.

old_path: Rakefile

new_path: Rakefile

diff:
@@ -147,6 +147,7 @@ namespace :docs do
Primer::SpinnerComponent,
Primer::SubheadComponent,
Primer::TextComponent,
+ Primer::TimeAgoComponent,
Primer::TimelineItemComponent,
Primer::TooltipComponent,
Primer::TruncateComponent,

diff:
-
+const t=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],e=["January","February","March","April","May","June","July","August","September","October","November","December"];function n(t){return"0".concat(t).slice(-2)}function r(a,o){const i=a.getDay(),c=a.getDate(),s=a.getMonth(),u=a.getFullYear(),m=a.getHours(),d=a.getMinutes(),l=a.getSeconds();return o.replace(/%([%aAbBcdeHIlmMpPSwyYZz])/g,(function(o){let h;switch(o[1]){case"%":return"%";case"a":return t[i].slice(0,3);case"A":return t[i];case"b":return e[s].slice(0,3);case"B":return e[s];case"c":return a.toString();case"d":return n(c);case"e":return String(c);case"H":return n(m);case"I":return n(r(a,"%l"));case"l":return String(0===m||12===m?12:(m+12)%12);case"m":return n(s+1);case"M":return n(d);case"p":return m>11?"PM":"AM";case"P":return m>11?"pm":"am";case"S":return n(l);case"w":return String(i);case"y":return n(u%100);case"Y":return String(u);case"Z":return h=a.toString().match(/\((\w+)\)$/),h?h[1]:"";case"z":return h=a.toString().match(/\w([+-]\d\d\d\d) /),h?h[1]:""}return""}))}function a(t){let e;return function(){if(e)return e;if("Intl"in window)try{return e=new Intl.DateTimeFormat(void 0,t),e}catch(t){if(!(t instanceof RangeError))throw t}}}let o=null;const i=a({day:"numeric",month:"short"});function c(){if(null!==o)return o;const t=i();if(t){const e=t.format(new Date(0));return o=!!e.match(/^\d/),o}return!1}let s=null;const u=a({day:"numeric",month:"short",year:"numeric"});function m(t){const e=t.closest("[lang]");return e instanceof HTMLElement&&e.lang?e.lang:"default"}const d=new WeakMap;class l extends HTMLElement{static get observedAttributes(){return["datetime","day","format","lang","hour","minute","month","second","title","weekday","year"]}connectedCallback(){const t=this.getFormattedTitle();t&&!this.hasAttribute("title")&&this.setAttribute("title",t);const e=this.getFormattedDate();e&&(this.textContent=e)}attributeChangedCallback(t,e,n){const r=this.getFormattedTitle();if("datetime"===t){const t=Date.parse(n);isNaN(t)?d.delete(this):d.set(this,new Date(t))}const a=this.getFormattedTitle(),o=this.getAttribute("title");"title"===t||!a||o&&o!==r||this.setAttribute("title",a);const i=this.getFormattedDate();i&&(this.textContent=i)}get date(){return d.get(this)}getFormattedTitle(){const t=this.date;if(!t)return;const e=h();if(e)return e.format(t);try{return t.toLocaleString()}catch(e){if(e instanceof RangeError)return t.toString();throw e}}getFormattedDate(){}}const h=a({day:"numeric",month:"short",year:"numeric",hour:"numeric",minute:"2-digit",timeZoneName:"short"}),g=new WeakMap;class w extends l{attributeChangedCallback(t,e,n){"hour"!==t&&"minute"!==t&&"second"!==t&&"time-zone-name"!==t||g.delete(this),super.attributeChangedCallback(t,e,n)}getFormattedDate(){const t=this.date;if(!t)return;const e=function(t,e){const n={weekday:{short:"%a",long:"%A"},day:{numeric:"%e","2-digit":"%d"},month:{short:"%b",long:"%B"},year:{numeric:"%Y","2-digit":"%y"}};let a=c()?"weekday day month year":"weekday month day, year";for(const e in n){const r=n[e][t.getAttribute(e)];a=a.replace(e,r||"")}return a=a.replace(/(\s,)|(,\s$)/,""),r(e,a).replace(/\s+/," ").trim()}(this,t)||"",n=function(t,e){const n={},o=t.getAttribute("hour");"numeric"!==o&&"2-digit"!==o||(n.hour=o);const i=t.getAttribute("minute");"numeric"!==i&&"2-digit"!==i||(n.minute=i);const c=t.getAttribute("second");"numeric"!==c&&"2-digit"!==c||(n.second=c);const s=t.getAttribute("time-zone-name");"short"!==s&&"long"!==s||(n.timeZoneName=s);if(0===Object.keys(n).length)return;let u=g.get(t);u||(u=a(n),g.set(t,u));const m=u();if(m)return m.format(e);return r(e,n.second?"%H:%M:%S":"%H:%M")}(this,t)||"";return"".concat(e," ").concat(n).trim()}}window.customElements.get("local-time")||(window.LocalTimeElement=w,window.customElements.define("local-time",w));class f{constructor(t,e){this.date=t,this.locale=e}toString(){const t=this.timeElapsed();if(t)return t;{const t=this.timeAhead();return t||"on ".concat(this.formatDate())}}timeElapsed(){const t=(new Date).getTime()-this.date.getTime(),e=Math.round(t/1e3),n=Math.round(e/60),r=Math.round(n/60),a=Math.round(r/24);return t>=0&&a<30?this.timeAgoFromMs(t):null}timeAhead(){const t=this.date.getTime()-(new Date).getTime(),e=Math.round(t/1e3),n=Math.round(e/60),r=Math.round(n/60),a=Math.round(r/24);return t>=0&&a<30?this.timeUntil():null}timeAgo(){const t=(new Date).getTime()-this.date.getTime();return this.timeAgoFromMs(t)}timeAgoFromMs(t){const e=Math.round(t/1e3),n=Math.round(e/60),r=Math.round(n/60),a=Math.round(r/24),o=Math.round(a/30),i=Math.round(o/12);return t<0||e<10?y(this.locale,0,"second"):e<45?y(this.locale,-e,"second"):e<90||n<45?y(this.locale,-n,"minute"):n<90||r<24?y(this.locale,-r,"hour"):r<36||a<30?y(this.locale,-a,"day"):o<18?y(this.locale,-o,"month"):y(this.locale,-i,"year")}microTimeAgo(){const t=(new Date).getTime()-this.date.getTime(),e=Math.round(t/1e3),n=Math.round(e/60),r=Math.round(n/60),a=Math.round(r/24),o=Math.round(a/30),i=Math.round(o/12);return n<1?"1m":n<60?"".concat(n,"m"):r<24?"".concat(r,"h"):a<365?"".concat(a,"d"):"".concat(i,"y")}timeUntil(){const t=this.date.getTime()-(new Date).getTime();return this.timeUntilFromMs(t)}timeUntilFromMs(t){const e=Math.round(t/1e3),n=Math.round(e/60),r=Math.round(n/60),a=Math.round(r/24),o=Math.round(a/30),i=Math.round(o/12);return o>=18||o>=12?y(this.locale,i,"year"):a>=45||a>=30?y(this.locale,o,"month"):r>=36||r>=24?y(this.locale,a,"day"):n>=90||n>=45?y(this.locale,r,"hour"):e>=90||e>=45?y(this.locale,n,"minute"):y(this.locale,e>=10?e:0,"second")}microTimeUntil(){const t=this.date.getTime()-(new Date).getTime(),e=Math.round(t/1e3),n=Math.round(e/60),r=Math.round(n/60),a=Math.round(r/24),o=Math.round(a/30),i=Math.round(o/12);return a>=365?"".concat(i,"y"):r>=24?"".concat(a,"d"):n>=60?"".concat(r,"h"):n>1?"".concat(n,"m"):"1m"}formatDate(){let t=c()?"%e %b":"%b %e";var e;return e=this.date,(new Date).getUTCFullYear()!==e.getUTCFullYear()&&(t+=function(){if(null!==s)return s;const t=u();if(t){const e=t.format(new Date(0));return s=!!e.match(/\d,/),s}return!0}()?", %Y":" %Y"),r(this.date,t)}formatTime(){const t=M();return t?t.format(this.date):r(this.date,"%l:%M%P")}}function y(t,e,n){const r=function(t,e){if("Intl"in window&&"RelativeTimeFormat"in window.Intl)try{return new Intl.RelativeTimeFormat(t,e)}catch(t){if(!(t instanceof RangeError))throw t}}(t,{numeric:"auto"});return r?r.format(e,n):function(t,e){if(0===t)switch(e){case"year":case"quarter":case"month":case"week":return"this ".concat(e);case"day":return"today";case"hour":case"minute":return"in 0 ".concat(e,"s");case"second":return"now"}else if(1===t)switch(e){case"year":case"quarter":case"month":case"week":return"next ".concat(e);case"day":return"tomorrow";case"hour":case"minute":case"second":return"in 1 ".concat(e)}else if(-1===t)switch(e){case"year":case"quarter":case"month":case"week":return"last ".concat(e);case"day":return"yesterday";case"hour":case"minute":case"second":return"1 ".concat(e," ago")}else if(t>1)switch(e){case"year":case"quarter":case"month":case"week":case"day":case"hour":case"minute":case"second":return"in ".concat(t," ").concat(e,"s")}else if(t<-1)switch(e){case"year":case"quarter":case"month":case"week":case"day":case"hour":case"minute":case"second":return"".concat(-t," ").concat(e,"s ago")}throw new RangeError("Invalid unit argument for format() '".concat(e,"'"))}(e,n)}const M=a({hour:"numeric",minute:"2-digit"});class b extends l{getFormattedDate(){const t=this.date;if(t)return new f(t,m(this)).toString()}connectedCallback(){T.push(this),A||(D(),A=setInterval(D,6e4)),super.connectedCallback()}disconnectedCallback(){const t=T.indexOf(this);-1!==t&&T.splice(t,1),T.length||A&&(clearInterval(A),A=null)}}const T=[];let A;function D(){let t,e,n;for(e=0,n=T.length;e<n;e++)t=T[e],t.textContent=t.getFormattedDate()||""}window.customElements.get("relative-time")||(window.RelativeTimeElement=b,window.customElements.define("relative-time",b));class F extends b{getFormattedDate(){const t=this.getAttribute("format"),e=this.date;if(e)return"micro"===t?new f(e,m(this)).microTimeAgo():new f(e,m(this)).timeAgo()}}window.customElements.get("time-ago")||(window.TimeAgoElement=F,window.customElements.define("time-ago",F));class E extends b{getFormattedDate(){const t=this.getAttribute("format"),e=this.date;if(e)return"micro"===t?new f(e,m(this)).microTimeUntil():new f(e,m(this)).timeUntil()}}window.customElements.get("time-until")||(window.TimeUntilElement=E,window.customElements.define("time-until",E));

diff: +import "./time_ago_component"

diff:
+# frozen_string_literal: true
+
+module Primer
+ # A basic wrapper for the "<time-ago>" element.
+ class TimeAgoComponent < Primer::Component
+ def initialize(time:, micro: false, **system_arguments)
+ @system_arguments = system_arguments
+ @system_arguments[:datetime] = time.utc.iso8601
+ @system_arguments[:classes] = class_names("no-wrap", @system_arguments[:classes])
+ @system_arguments[:tag] = "time-ago"
+ @system_arguments[:format] = "micro" if micro
+ @content = if micro
+ micro_time_ago(time)
+ else
+ time.in_time_zone.strftime("%b %-d, %Y")
+ end
+ end
+
+ def call
+ render(Primer::BaseComponent.new(**@system_arguments)) { @content }
+ end
+
+ private
+
+ def micro_time_ago(time)
+ seconds_ago = Time.current - time
+
+ if seconds_ago < 1.minute
+ "1m"
+ elsif seconds_ago >= 1.minute && seconds_ago < 1.hour
+ "#{(seconds_ago / 60).floor}m"
+ elsif seconds_ago >= 1.hour && seconds_ago < 1.day
+ "#{(seconds_ago / 60 / 60).floor}h"
+ elsif seconds_ago >= 1.day && seconds_ago < 1.year
+ "#{(seconds_ago / 60 / 60 / 24).floor}d"
+ elsif seconds_ago >= 1.year
+ "#{(seconds_ago / 60 / 60 / 24 / 365).floor}y"
+ end
+ end
+ end
+end

diff: +import {TimeAgoElement} from '@github/time-elements'

diff:
+class TimeAgoComponentPreview < ViewComponent::Preview
+ def default
+ render(Primer::TimeAgoComponent.new(time: Time.new))
+ end
+
+ def micro
+ render(Primer::TimeAgoComponent.new(time: Time.new, micro: true))
+ end
+end

diff:
url: /components/subhead
- title: Text
url: /components/text
+ - title: TimeAgo
+ url: /components/timeago
- title: TimelineItem
url: /components/timelineitem
- title: Tooltip

diff:
"rollup-plugin-terser": "^7.0.2",
"tslib": "^2.1.0",
"typescript": "^4.1.5"
+ },
+ "dependencies": {
+ "@github/time-elements": "^3.1.1"
}
}

diff:
+# frozen_string_literal: true
+
+class Primer::TimeAgoComponentStories < ViewComponent::Storybook::Stories
+ layout "storybook_preview"
+
+ story(:time_ago) do
+ controls do
+ time Time.zone.now
+ end
+ end
+end

diff:
@@ -49,6 +49,7 @@ class PrimerComponentTest < Minitest::Test
[Primer::SubheadComponent, { heading: "Foo" }, proc { |component| component.slot(:heading) { "Foo" } }],
[Primer::TextComponent, {}],
[Primer::TruncateComponent, {}],
+ [Primer::TimeAgoComponent, { time: Time.zone.now }],
[Primer::TimelineItemComponent, {}, proc { |component| component.body { "Foo" } }],
[Primer::TooltipComponent, { label: "More" }],
[Primer::UnderlineNavComponent, {}]

diff:
+import {expect} from 'chai'
+import {componentTest} from './helpers.js'
+
+componentTest('time_ago_component', it => {
+
+ it('works', async page => {
+ await expect(page).to.have.selector('body')
+ const el = await page.$('body')
+ await expect(el).to.be.visible()
+ })
+
+})

diff:
+# frozen_string_literal: true
+
+require "test_helper"
+
+class PrimerTimeAgoComponentTest < Minitest::Test
+ include Primer::ComponentTestHelpers
+
+ def test_renders_time_ago_element
+ render_inline(Primer::TimeAgoComponent.new(time: Time.zone.now))
+
+ assert_selector("time-ago[datetime][class]", count: 1)
+ end
+
+ def reflects_datetime_attr_and_contents_based_on_date
+ render_inline(Primer::TimeAgoComponent.new(time: Time.utc(2021, 1, 1, 9, 10)))
+
+ assert_selector("time-ago[datetime=\"2021-01-01T09:10:00Z\"]", count: 1, text: "Jan 1, 2021")
+ end
+
+ def sets_contents_to_short_representation_with_micro_eq_true
+ Timecop.freeze("2021-01-30T15:00:00Z") do
+ render_inline(Primer::TimeAgoComponent.new(time: Time.utc(2021, 1, 1, 9, 10), micro: true))
+
+ assert_selector("time-ago[datetime=\"2021-01-01T09:10:00Z\"]", count: 1, text: "29d")
+ end
+ end
+
+ def returns_number_of_mins_ago_in_micro_format
+ Timecop.freeze("2021-01-01T09:50:00Z") do
+ render_inline(Primer::TimeAgoComponent.new(time: Time.utc(2021, 1, 1, 9, 10), micro: true))
+
+ assert_selector("time-ago[datetime=\"2021-01-01T09:10:00Z\"]", count: 1, text: "40m")
+ end
+ end
+
+ def returns_number_of_hours_ago_in_micro_format
+ Timecop.freeze("2021-01-01T15:00:00Z") do
+ render_inline(Primer::TimeAgoComponent.new(time: Time.utc(2021, 1, 1, 9, 10), micro: true))
+
+ assert_selector("time-ago[datetime=\"2021-01-01T09:10:00Z\"]", count: 1, text: "5h")
+ end
+ end
+
+ def returns_number_of_days_ago_in_micro_format
+ Timecop.freeze("2021-09-30T15:00:00Z") do
+ render_inline(Primer::TimeAgoComponent.new(time: Time.utc(2021, 1, 1, 9, 10), micro: true))
+
+ assert_selector("time-ago[datetime=\"2021-01-01T09:10:00Z\"]", count: 1, text: "d")
+ end
+ end
+
+ def returns_number_of_years_ago_in_micro_format
+ Timecop.freeze("2037-09-30T15:00:00Z") do
+ render_inline(Primer::TimeAgoComponent.new(time: Time.utc(2021, 1, 1, 9, 10), micro: true))
+
+ assert_selector("time-ago[datetime=\"2021-01-01T09:10:00Z\"]", count: 1, text: "16y")
+ end
+ end
+end

diff:
+# frozen_string_literal: true
+
+require "application_system_test_case"
+
+class IntegrationTimeAgoComponentTest < ApplicationSystemTestCase
+ def test_render
+ with_preview(:default)
+
+ assert_selector("time-ago", text: "now")
+ end
+
+ def test_render_micro
+ with_preview(:micro)
+
+ assert_selector("time-ago", text: "1m")
+ end
+end

diff:
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@github/time-elements@^3.1.1":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@github/time-elements/-/time-elements-3.1.1.tgz#b52be933a87a0b585a5df6fd2dea6ccda5d776b9"
+ integrity sha512-95TquDedxMpNb8BUrVIt9NEfdNBw4DRjdcECDPx4Ix6gkksI2npKRJBOV9XJMYjEIBo50iAiJzUuwL/fh3ptFw==
+
"@rollup/plugin-node-resolve@^11.2.0":
version "11.2.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.0.tgz#a5ab88c35bb7622d115f44984dee305112b6f714"

original_message: feat(TimeAgo): add TimeAgoComponent


message: chore: remove legacy test files

change_type: DELETE

diff:
-import {expect} from 'chai'
-import {componentTest} from './helpers.js'
-
-componentTest('time_ago_component', it => {
-
- it('works', async page => {
- await expect(page).to.have.selector('body')
- const el = await page.$('body')
- await expect(el).to.be.visible()
- })
-
-})

original_message: chore: remove legacy test files


message: docs(time_ago): add Yard docs

diff:
# frozen_string_literal: true
module Primer
- # A basic wrapper for the "<time-ago>" element.
+ # Use Primer::TimeAgoComponent to display a time relative to how long ago it was
class TimeAgoComponent < Primer::Component
+
+ #
+ # @example auto|Default
+ # <%= render(Primer::TimeAgoComponent.new(time: Time.zone.now)) %>
+ #
+ # @param time [Time] The time to be formatted
+ # @param micro [Boolean] If true then the text will be formatted in "micro" mode, using as few characters as possible
def initialize(time:, micro: false, **system_arguments)
@system_arguments = system_arguments
@system_arguments[:datetime] = time.utc.iso8601

original_message: docs(time_ago): add Yard docs


message: refactor(time_ago): move all content logic into function

diff:
@@ -16,21 +16,20 @@ module Primer
@system_arguments[:classes] = class_names("no-wrap", @system_arguments[:classes])
@system_arguments[:tag] = "time-ago"
@system_arguments[:format] = "micro" if micro
- @content = if micro
- micro_time_ago(time)
- else
- time.in_time_zone.strftime("%b %-d, %Y")
- end
+ @time = time
+ @micro = micro
end
def call
- render(Primer::BaseComponent.new(**@system_arguments)) { @content }
+ render(Primer::BaseComponent.new(**@system_arguments)) { time_in_words }
end
private
- def micro_time_ago(time)
- seconds_ago = Time.current - time
+ def time_in_words
+ return @time.in_time_zone.strftime("%b %-d, %Y") unless @micro
+
+ seconds_ago = Time.current - @time
if seconds_ago < 1.minute
"1m"


diff:
@@ -10,6 +10,7 @@ module Primer
#
# @param time [Time] The time to be formatted
# @param micro [Boolean] If true then the text will be formatted in "micro" mode, using as few characters as possible
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
def initialize(time:, micro: false, **system_arguments)
@system_arguments = system_arguments
@system_arguments[:datetime] = time.utc.iso8601

original_message:
docs(time_ago): add system_aguments hash

Co-authored-by: Manuel Puyol <manuelpuyol@github.com>


diff:
# frozen_string_literal: true
module Primer
- # Use Primer::TimeAgoComponent to display a time relative to how long ago it was
+ # Use Primer::TimeAgoComponent to display a time relative to how long ago it was. This component requires JavaScript.
class TimeAgoComponent < Primer::Component
#

original_message:
Update app/components/time_ago_component.rb

Co-authored-by: Simon Taranto <srt32@users.noreply.github.com>


message: build(npm): ensure only necessary files are added

diff:
"bugs": {
"url": "https://github.com/primer/view_components/issues"
},
- "files": [
- "app/assets/javascripts"
- ],
+ "files": ["app/components/primer/*.js"],
"scripts": {
"prepare": "tsc && rollup -c",
"lint": "eslint app/components",


message: build: use npm to set its own version, rather than sed

old_path: script/release-npm

new_path: script/release-npm

diff:
@@ -9,10 +9,7 @@ read -p "MINOR: " minor
read -p "PATCH: " patch
# Update package.json version
-sed -E -i.bak "s/\"version\": \"(.*)?\"/\"version\": \"$major.$minor.$patch\"/g" package.json
-
-# Remove bak files
-rm package.json.bak
+npm version --no-git-tag-version "$major.$minor.$patch"
# Update lock and assets
yarn


message: chore: fixup npm version

diff:
{
- "name": "@primer/view-components",
- "version": "0.0.1",
+ "name": "@primer/view_components",
+ "version": "0.0.20",
"description": "ViewComponents for the Primer Design System",
"main": "app/assets/javascripts/primer_view_components.js",
"module": "app/components/primer/primer.js",

original_message: chore: fixup npm version


message: build: rewrite release script

diff:
@@ -11,3 +11,7 @@ module Primer
end
end
end
+
+if __FILE__ == $0
+ puts Primer::ViewComponents::VERSION::STRING
+end

old_path: script/release

new_path: script/release

diff:
#!/usr/bin/env bash
-read -p "Bump gem (y/N)? " answer
-case ${answer:0:1} in
- y|Y )
- bump_gem=1
- ;;
- * )
- bump_gem=0
- ;;
-esac
-
-read -p "Bump NPM (y/N)? " answer
-case ${answer:0:1} in
- y|Y )
- bump_npm=1
- ;;
- * )
- bump_npm=0
- ;;
-esac
-
-# Create release branch
+create_release_branch() {
git checkout -B release
+}
-if [[ $bump_gem -eq 1 ]]
-then
- script/release-gem
+update_readme() {
+ sed -i "/## main/ {a\
+ \\
+ \\
+ ## $1.$2.$3
+ }" CHANGELOG.md
+}
- git add \
- CHANGELOG.md \
- Gemfile.lock \
- demo/Gemfile.lock \
+update_ruby_version() {
+ # Update version file
+ sed -E -i.bak \
+ -e "s/MAJOR = [0-9]+/MAJOR = $1/g" \
+ -e "s/MINOR = [0-9]+/MINOR = $2/g" \
+ -e "s/PATCH = [0-9]+/PATCH = $3/g" \
lib/primer/view_components/version.rb
-fi
+}
-if [[ $bump_npm -eq 1 ]]
-then
- script/release-npm
+update_gemfiles() {
+ # Update Gemfile.lock
+ bundle
+ pushd demo
+ bundle
+ popd
+}
+
+update_npm() {
+ npm version --no-git-tag-version "$1.$2.$3"
+ yarn
+}
+
+add_changed_files() {
git add \
+ CHANGELOG.md \
+ Gemfile.lock \
+ demo/Gemfile.lock \
+ lib/primer/view_components/version.rb \
+ app/assets/javascripts \
package.json \
- app/assets/javascripts/
-fi
+ yarn.lock
+}
-# Commit and push
-git commit -m "release"
-# git push origin release
+commit() {
+ git commit -m "release $1.$2.$3"
+ git tag "v$1.$2.$3"
+}
+push() {
echo "####################################################"
echo "Now, open a PR with this branch and merge it to main"
echo "Then, run script/publish on main to release the gem and the NPM package"
echo "Finally, create a GitHub release https://github.com/primer/view_components/releases/new with the changes from CHANGELOG"
echo "####################################################"
+}
+
+main() {
+ version=$(ruby ./lib/primer/view_components/version.rb)
+ version=(${version//./ })
+ major=${version[0]}
+ minor=${version[1]}
+ patch=${version[2]}
+
+ echo "Type the number of an option to bump, or pick Manual to enter a version number"
+ select bump in Major Minor Patch Manual
+ do
+ if [ "$bump" == "Major" ]; then
+ major=$((major + 1))
+ elif [ "$bump" == "Minor" ]; then
+ minor=$((minor + 1))
+ elif [ "$bump" == "Patch" ]; then
+ patch=$((patch + 1))
+ else
+ read -p "What version? (Currently $major.$minor.$patch): " new_version
+ if [ "$new_version" == "$major.$minor.$patch" ]; then
+ echo "Error: Cannot be the same version"
+ exit 1
+ fi
+
+ new_version=(${new_version//./ })
+
+ major=${new_version[0]}
+ minor=${new_version[1]}
+ patch=${new_version[2]}
+ fi
+
+ echo new version $major $minor $patch
+
+ create_release_branch
+ update_readme $major $minor $patch
+ update_ruby_version $major $minor $patch
+ update_gemfiles $major $minor $patch
+ update_npm $major $minor $patch
+ add_changed_files
+ commit
+ push
+ done
+}
+
+main

old_path: script/release-gem

diff:
-#!/usr/bin/env bash
-
-echo "####################################################"
-echo "GEM version:"
-echo "####################################################"
-
-read -p "MAJOR: " major
-read -p "MINOR: " minor
-read -p "PATCH: " patch
-
-# Update version file
-sed -E -i.bak \
- -e "s/MAJOR = [0-9]+/MAJOR = $major/g" \
- -e "s/MINOR = [0-9]+/MINOR = $minor/g" \
- -e "s/PATCH = [0-9]+/PATCH = $patch/g" \
- lib/primer/view_components/version.rb
-
-# Update Changelog
-sed -i.bak "/## main/ {a\
-\\
-\\
-## $major.$minor.$patch
-}" CHANGELOG.md
-
-# Remove bak files
-rm CHANGELOG.md.bak
-rm lib/primer/view_components/version.rb.bak
-
-# Update Gemfile.lock
-bundle
-pushd demo
-bundle
-popd

diff:
-#!/usr/bin/env bash
-
-echo "####################################################"
-echo "NPM version:"
-echo "####################################################"
-
-read -p "MAJOR: " major
-read -p "MINOR: " minor
-read -p "PATCH: " patch
-
-# Update package.json version
-npm version --no-git-tag-version "$major.$minor.$patch"
-
-# Update lock and assets
-yarn

original_message: build: rewrite release script


old_path: script/publish

new_path: script/publish

diff:
#!/usr/bin/env bash
# Publish gem
-read -p "Publish gem (y/N)? " answer
-case ${answer:0:1} in
- y|Y )
bundle exec rake release
- ;;
- * )
- # noop
- ;;
-esac
# Publish NPM
-read -p "Publish NPM (y/N)? " answer
-case ${answer:0:1} in
- y|Y )
npm publish --access public
- ;;
- * )
- # noop
- ;;
-esac

original_message:
build(publish): always publish both packages

Co-authored-by: Manuel Puyol <manuelpuyol@github.com>


message: chore: bump package.json version

diff:
{
"name": "@primer/view_components",
- "version": "0.0.20",
+ "version": "0.0.21",
"description": "ViewComponents for the Primer Design System",
"main": "app/assets/javascripts/primer_view_components.js",
"module": "app/components/primer/primer.js",

original_message:
chore: bump package.json version

Co-authored-by: Manuel Puyol <manuelpuyol@github.com>


message: build(release): push to origin

diff:
@@ -51,6 +51,8 @@ commit() {
}
push() {
+ git push origin release
+
echo "####################################################"
echo "Now, open a PR with this branch and merge it to main"
echo "Then, run script/publish on main to release the gem and the NPM package"

original_message:
build(release): push to origin

Co-authored-by: Manuel Puyol <manuelpuyol@github.com>


message: style: fix rubocop warnings

diff:
@@ -12,6 +12,6 @@ module Primer
end
end
-if __FILE__ == $0
- puts Primer::ViewComponents::VERSION::STRING
-end
+# rubocop:disable Rails/Output
+puts Primer::ViewComponents::VERSION::STRING if __FILE__ == $PROGRAM_NAME
+# rubocop:enable Rails/Output

original_message: style: fix rubocop warnings


message: build(release): check for clean wd before running

diff:
#!/usr/bin/env bash
+working_tree_is_clean() {
+ status=$(git status --porcelain)
+ [ "$status" == "" ]
+}
+
create_release_branch() {
git checkout -B release
}
@@ -67,6 +72,15 @@ main() {
minor=${version[1]}
patch=${version[2]}
+ echo "==================="
+ echo "Prerequisite Checks"
+ echo "==================="
+
+ if ! working_tree_is_clean; then
+ echo "Error: unclean working tree"
+ exit 1
+ fi
+
echo "Type the number of an option to bump, or pick Manual to enter a version number"
select bump in Major Minor Patch Manual
do


message: build(release): exit if proposed version exists on remote

diff:
#!/usr/bin/env bash
+fetch() {
+ git fetch --all
+}
+
+tag_exists_on_remote() {
+ git rev-parse --quiet --verify refs/tags/v$1.$2.$3 > /dev/null
+}
+
working_tree_is_clean() {
status=$(git status --porcelain)
[ "$status" == "" ]
@@ -76,6 +84,7 @@ main() {
echo "Prerequisite Checks"
echo "==================="
+ fetch
if ! working_tree_is_clean; then
echo "Error: unclean working tree"
exit 1
@@ -106,6 +115,11 @@ main() {
echo new version $major $minor $patch
+ if tag_exists_on_remote $major $minor $patch; then
+ echo "Error: tag exists on remote"
+ exit 1
+ fi
+
create_release_branch
update_readme $major $minor $patch
update_ruby_version $major $minor $patch


message: build(release): print out release banner

diff:
@@ -113,13 +113,15 @@ main() {
patch=${new_version[2]}
fi
- echo new version $major $minor $patch
-
if tag_exists_on_remote $major $minor $patch; then
echo "Error: tag exists on remote"
exit 1
fi
+ echo "==============================="
+ echo "Creating release for $major.$minor.$patch"
+ echo "==============================="
+
create_release_branch
update_readme $major $minor $patch
update_ruby_version $major $minor $patch


message: build(release): fixup sed commands

diff:
@@ -18,7 +18,7 @@ create_release_branch() {
}
update_readme() {
- sed -i "/## main/ {a\
+ sed -i '' "/## main/ {a\
\\
\\
## $1.$2.$3
@@ -27,7 +27,7 @@ update_readme() {
update_ruby_version() {
# Update version file
- sed -E -i.bak \
+ sed -E -i '' \
-e "s/MAJOR = [0-9]+/MAJOR = $1/g" \
-e "s/MINOR = [0-9]+/MINOR = $2/g" \
-e "s/PATCH = [0-9]+/PATCH = $3/g" \

original_message: build(release): fixup sed commands


message: build(release): pass major/minor/patch to all sub command

diff:
@@ -122,14 +122,14 @@ main() {
echo "Creating release for $major.$minor.$patch"
echo "==============================="
- create_release_branch
+ create_release_branch $major $minor $patch
update_readme $major $minor $patch
update_ruby_version $major $minor $patch
update_gemfiles $major $minor $patch
update_npm $major $minor $patch
- add_changed_files
- commit
- push
+ add_changed_files $major $minor $patch
+ commit $major $minor $patch
+ push $major $minor $patch
done
}


message: build(release): exit early if changes need pulling

diff:
@@ -4,6 +4,11 @@ fetch() {
git fetch --all
}
+remote_history_is_clean() {
+ history =$(git rev-list --count --left-only @{u}...HEAD)
+ [ "$history" == "0" ]
+}
+
tag_exists_on_remote() {
git rev-parse --quiet --verify refs/tags/v$1.$2.$3 > /dev/null
}
@@ -85,6 +90,11 @@ main() {
echo "==================="
fetch
+ if ! remote_history_is_clean; then
+ echo "Error: changes exist on origin not pulled into this branch. Please pull"
+ exit 1
+ fi
+
if ! working_tree_is_clean; then
echo "Error: unclean working tree"
exit 1


message: build(release): exit early if changes need pushing

diff:
@@ -4,6 +4,11 @@ fetch() {
git fetch --all
}
+local_history_is_clean() {
+ history=$(git rev-list --count --right-only @{u}...HEAD)
+ [ "$history" == "0" ]
+}
+
remote_history_is_clean() {
history=$(git rev-list --count --left-only @{u}...HEAD)
[ "$history" == "0" ]
@@ -95,6 +100,11 @@ main() {
exit 1
fi
+ if ! local_history_is_clean; then
+ echo "Error: changes exist that haven't been pushed to origin. Please pull"
+ exit 1
+ fi
+
if ! working_tree_is_clean; then
echo "Error: unclean working tree"
exit 1


message: build(release): exit earlier on unclean wd

diff:
@@ -94,6 +94,11 @@ main() {
echo "Prerequisite Checks"
echo "==================="
+ if ! working_tree_is_clean; then
+ echo "Error: unclean working tree"
+ exit 1
+ fi
+
fetch
if ! remote_history_is_clean; then
echo "Error: changes exist on origin not pulled into this branch. Please pull"
@@ -105,11 +110,6 @@ main() {
exit 1
fi
- if ! working_tree_is_clean; then
- echo "Error: unclean working tree"
- exit 1
- fi
-
echo "Type the number of an option to bump, or pick Manual to enter a version number"
select bump in Major Minor Patch Manual
do


message: build(release): exit early if not on main branch

diff:
@@ -4,6 +4,10 @@ fetch() {
git fetch --all
}
+branch_name() {
+ git symbolic-ref --short HEAD
+}
+
local_history_is_clean() {
history=$(git rev-list --count --right-only @{u}...HEAD)
[ "$history" == "0" ]
@@ -99,6 +103,11 @@ main() {
exit 1
fi
+ if [ "$(branch_name)" !== "main" ]; then
+ echo "Error: can only make a release on the main branch"
+ exit 1
+ fi
+
fetch
if ! remote_history_is_clean; then
echo "Error: changes exist on origin not pulled into this branch. Please pull"


message:
docs(contributing): remove comment about being on right branch
This is now automated. No need

diff:
@@ -31,7 +31,6 @@ Here are a few things you can do that will increase the likelihood of your pull
If you are the current maintainer of this gem:
-1. Checkout to `main` and `git pull`
1. Run `./script/release` and follow the instructions.
## Criteria for changing component status


message: test: move TimeAgoComponentPreview into Primer subfolder

diff:
+module Primer
+ class TimeAgoComponentPreview < ViewComponent::Preview
+ def default
+ render(Primer::TimeAgoComponent.new(time: Time.new))
+ end
+
+ def micro
+ render(Primer::TimeAgoComponent.new(time: Time.new, micro: true))
+ end
+ end
+end

diff:
-class TimeAgoComponentPreview < ViewComponent::Preview
- def default
- render(Primer::TimeAgoComponent.new(time: Time.new))
- end
-
- def micro
- render(Primer::TimeAgoComponent.new(time: Time.new, micro: true))
- end
-end


message: style: eslint --fix .

diff:
-import "./tab_container_component"
-import "./time_ago_component"
+import './tab_container_component'
+import './time_ago_component'

diff:
-import {TimeAgoElement} from '@github/time-elements'
+import '@github/time-elements'

original_message: style: eslint --fix .


message: finalize ADR 0250

diff:
@@ -4,7 +4,7 @@ Date: 2021-02-19
## Status
-Proposed
+Accepted
## Context
@@ -33,11 +33,11 @@ Publishing a sidecar package like this moderately increases _complexity_ for thi
### Testing
-We will test our behaviours in the Browser environment (using System tests), as this is where they will be consumed. This will require using Browser APIs to automate testing. We will use test automation libraries that automate browsers via the [Devtools Protocol](https://chromedevtools.github.io/devtools-protocol/) such as [Puppeteer](https://pptr.dev/), [Playwright](https://playwright.dev/) or [Ferrum](https://ferrum.rocks/). Using a Devtools Protocol based tool gives us the best level of flexibility in testing, while also being compatible with current browsers.
+We will test our behaviours in the Browser environment (using Rails System tests), as this is where they will be consumed. This will require using Browser APIs to automate testing. We will use test automation libraries that automate browsers via the [Devtools Protocol](https://chromedevtools.github.io/devtools-protocol/) such as [Puppeteer](https://pptr.dev/), [Playwright](https://playwright.dev/) or [Ferrum](https://ferrum.rocks/). Using a Devtools Protocol based tool gives us the best level of flexibility in testing, while also being compatible with current browsers.
### Documentation
-We will document which components are behavioural, so that consumers are aware they are required to load the client side bundle code for these components to work properly. We will offer a documentation page on how to use the `javascript_include_tag` helper to load the bundle using the Asset Bundle, or otherwise import the npm package within their codebases.
+We will document which components are behavioural, so that consumers are aware they are required to load the client side bundle code for these components to work properly. We will offer a documentation page on how to use the `javascript_include_tag` helper to load the bundle using the Asset Pipeline, or otherwise import the npm package within their codebases.
## Consequences

original_message:
finalize ADR 0250

Co-authored-by: Joel Hawksley <joelhawksley@github.com>


message: docs(changelog): fix merge conflict problems

diff:
*Simon Taranto*
+* Add `TimeAgo` component.
+
+ *Keith Cirkel*
+
## 0.0.25
* Promote `SubheadComponent` to beta.
*Simon Taranto*
-* Add `TimeAgo` component.
-
- *Keith Cirkel*
-
* **Breaking change**: Upgrade `DetailsComponent` to use Slots V2.
*Simon Taranto*


message: docs(index): add JS install instructions

diff:
@@ -31,6 +31,18 @@ In `config/application.rb`, add **after the application definition**:
require "primer/view_components/engine"
```
+To add the JavaScript behaviours, in your `application.html.erb` in the `<head>` tag add:
+
+```erb
+<%= javascript_include_tag("primer") %>
+```
+
+Or alternatively, you can install the `@primer/view-components` npm package and in your JavaScript code add:
+
+```js
+import '@primer/view-components'
+```
+
## Dependencies
In addition to the dependencies declared in the `gemspec`, Primer ViewComponents assumes the presence of Primer CSS.


message: docs(index): add note on optionality

diff:
@@ -31,7 +31,7 @@ In `config/application.rb`, add **after the application definition**:
require "primer/view_components/engine"
```
-To add the JavaScript behaviours, in your `application.html.erb` in the `<head>` tag add:
+Optionally, to add the JavaScript behaviours, in your `application.html.erb` in the `<head>` tag add:
```erb
<%= javascript_include_tag("primer") %>

original_message:
docs(index): add note on optionality

Co-authored-by: Simon Taranto <srt32@users.noreply.github.com>


message: docs(timeago): use deterministic time

diff:
@@ -5,7 +5,7 @@ module Primer
class TimeAgoComponent < Primer::Component
#
# @example auto|Default
- # <%= render(Primer::TimeAgoComponent.new(time: Time.zone.now)) %>
+ # <%= render(Primer::TimeAgoComponent.new(time: Time.at(628232400))) %>
#
# @param time [Time] The time to be formatted
# @param micro [Boolean] If true then the text will be formatted in "micro" mode, using as few characters as possible

original_message: docs(timeago): use deterministic time


message: fix(stories): use correct date control

diff:
@@ -5,7 +5,7 @@ class Primer::TimeAgoComponentStories < ViewComponent::Storybook::Stories
story(:time_ago) do
controls do
- time Time.zone.now
+ date(:time, "Time")
end
end
end

original_message: fix(stories): use correct date control


message: docs(timeago): adjust heading

diff:
@@ -4,7 +4,7 @@ module Primer
# Use Primer::TimeAgoComponent to display a time relative to how long ago it was. This component requires JavaScript.
class TimeAgoComponent < Primer::Component
#
- # @example auto|Default
+ # @example Default
# <%= render(Primer::TimeAgoComponent.new(time: Time.at(628232400))) %>
#
# @param time [Time] The time to be formatted

original_message: docs(timeago): adjust heading


message: docs: add view-component JS to docs

diff:
@@ -18,6 +18,7 @@ function Head(props) {
<meta property="og:image" content={siteMetadata.imageUrl} />
<meta property="twitter:card" content="summary_large_image" />
<link href="https://unpkg.com/@primer/css-next@canary/dist/primer.css" rel="stylesheet" />
+ <script src="https://unpkg.com/@primer/view-components@0.0.26/app/assets/javascripts/primer_view_components.js"></script>
</Helmet>
)
}

original_message: docs: add view-component JS to docs


message: docs: always use latest PVC version

diff:
@@ -18,7 +18,7 @@ function Head(props) {
<meta property="og:image" content={siteMetadata.imageUrl} />
<meta property="twitter:card" content="summary_large_image" />
<link href="https://unpkg.com/@primer/css-next@canary/dist/primer.css" rel="stylesheet" />
- <script src="https://unpkg.com/@primer/view-components@0.0.26/app/assets/javascripts/primer_view_components.js"></script>
+ <script src="https://unpkg.com/@primer/view-components@latest/app/assets/javascripts/primer_view_components.js"></script>
</Helmet>
)
}

original_message: docs: always use latest PVC version


message: docs(time_ago): fix story invalid date

diff:
@@ -5,7 +5,7 @@ class Primer::TimeAgoComponentStories < ViewComponent::Storybook::Stories
story(:time_ago) do
controls do
- date(:time, "Time")
+ date(:time, Time.zone.now)
end
end
end

original_message: docs(time_ago): fix story invalid date


old_path: .gitignore

new_path: .gitignore

diff:
@@ -19,3 +19,4 @@ demo/config/master.key
primer_view_components*.gem
.DS_Store
app/components/**/*.js
+app/components/**/*.d.ts

diff:
"description": "ViewComponents for the Primer Design System",
"main": "app/assets/javascripts/primer_view_components.js",
"module": "app/components/primer/primer.js",
+ "types": "app/components/primer/primer.d.ts",
"repository": "primer/view_components",
"keywords": [
"rails",
"url": "https://github.com/primer/view_components/issues"
},
"files": [
- "app/components/primer/*.js"
+ "app/components/primer/*.js",
+ "app/components/primer/*.d.ts"
],
"scripts": {
"prepare": "tsc && rollup -c",

diff:
],
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
+ "declaration": true,
+ "declarationDir": "app/components/primer/",
"strict": true,
"baseUrl": ".",
"types": []

original_message:
build: add type declarations to package.json

Co-authored-by: Clay Miller <clay@smockle.com>


message:
Apply all avatar classes to the link wrapper if applicable
Otherwise, you get a square `a` around a circle `img`.

diff:
@@ -34,17 +34,25 @@ module Primer
@system_arguments[:height] = size
@system_arguments[:width] = size
- @system_arguments[:classes] = class_names(
- system_arguments[:classes],
- "avatar" => !href,
+ avatar_class_names = class_names(
+ "avatar",
"avatar--small" => size < SMALL_THRESHOLD,
"circle" => !square
)
+
+ if @href
+ @link_arguments = { href: href, classes: avatar_class_names }
+ else
+ @system_arguments[:classes] = class_names(
+ system_arguments[:classes],
+ avatar_class_names
+ )
+ end
end
def call
if @href
- render(Primer::LinkComponent.new(href: @href, classes: "avatar")) do
+ render(Primer::LinkComponent.new(**@link_arguments)) do
render(Primer::BaseComponent.new(**@system_arguments)) { content }
end
else

diff:
@@ -46,7 +46,7 @@ class PrimerAvatarComponentTest < Minitest::Test
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", square: true))
assert_selector("img.avatar")
- refute_selector(".CircleBadge")
+ refute_selector(".circle")
end
def test_renders_link_wrapper
@@ -58,6 +58,42 @@ class PrimerAvatarComponentTest < Minitest::Test
refute_selector("img.avatar")
end
+ def test_defaults_circle_link_wrapper
+ render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#"))
+
+ assert_selector("a.avatar.circle") do
+ assert_selector("img")
+ end
+ refute_selector("img.avatar")
+ end
+
+ def test_squared_link_wrapper
+ render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", square: true))
+
+ assert_selector("a.avatar") do
+ assert_selector("img")
+ end
+ refute_selector(".circle")
+ end
+
+ def test_adds_small_modifier_to_link_wrapper_when_size_is_less_than_threshold
+ render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", size: Primer::AvatarComponent::SMALL_THRESHOLD - 1))
+
+ assert_selector("a.avatar.avatar--small") do
+ assert_selector("img")
+ end
+ refute_selector("img.avatar")
+ end
+
+ def test_does_not_add_small_modifier_to_link_wrapper_when_size_is_greater_than_threshold
+ render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", size: Primer::AvatarComponent::SMALL_THRESHOLD + 1))
+
+ assert_selector("a.avatar") do
+ assert_selector("img")
+ end
+ refute_selector(".avatar--small")
+ end
+
def test_status
assert_component_state(Primer::AvatarComponent, :beta)
end


message: Update the AvatarComponent docs

diff:
@@ -33,7 +33,7 @@ for organizations or any other non-human avatars.
### Link
-<Example src="" />
+<Example src="" />
```erb
<%= render(Primer::AvatarComponent.new(href: "#", src: "http://placekitten.com/200/200", alt: "@kittenuser")) %>

original_message: Update the AvatarComponent docs


message: Apply given custom AvatarComponent classes to the link if present

diff:
@@ -34,29 +34,21 @@ module Primer
@system_arguments[:height] = size
@system_arguments[:width] = size
- avatar_class_names = class_names(
+ @classes = class_names(
+ system_arguments.delete(:classes),
"avatar",
"avatar--small" => size < SMALL_THRESHOLD,
"circle" => !square
)
-
- if @href
- @link_arguments = { href: href, classes: avatar_class_names }
- else
- @system_arguments[:classes] = class_names(
- system_arguments[:classes],
- avatar_class_names
- )
- end
end
def call
if @href
- render(Primer::LinkComponent.new(**@link_arguments)) do
+ render(Primer::LinkComponent.new(classes: @classes, href: @href)) do
render(Primer::BaseComponent.new(**@system_arguments)) { content }
end
else
- render(Primer::BaseComponent.new(**@system_arguments)) { content }
+ render(Primer::BaseComponent.new(classes: @classes, **@system_arguments)) { content }
end
end
end

diff:
@@ -94,6 +94,15 @@ class PrimerAvatarComponentTest < Minitest::Test
refute_selector(".avatar--small")
end
+ def test_adds_custom_classes_to_link_wrapper
+ render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", classes: "custom-class"))
+
+ assert_selector("a.avatar.custom-class") do
+ assert_selector("img")
+ end
+ refute_selector("img.custom-class")
+ end
+
def test_status
assert_component_state(Primer::AvatarComponent, :beta)
end


message: Add AvatarComponent classes fix to the changelog

diff:
*Manuel Puyol*
+* Fix `AvatarComponent` to apply classes to the link wrapper if present.
+
+ *Steve Richert*
+
## 0.0.27
* Promote `BreadcrumbComponent` and `ProgressBarComponent` to beta status.


message: Refactor AvatarComponent not to mutate the given arguments

diff:
@@ -33,9 +33,8 @@ module Primer
@system_arguments[:size] = size
@system_arguments[:height] = size
@system_arguments[:width] = size
-
- @classes = class_names(
- system_arguments.delete(:classes),
+ @system_arguments[:classes] = class_names(
+ system_arguments[:classes],
"avatar",
"avatar--small" => size < SMALL_THRESHOLD,
"circle" => !square
@@ -44,8 +43,8 @@ module Primer
def call
if @href
- render(Primer::LinkComponent.new(classes: @classes, href: @href)) do
- render(Primer::BaseComponent.new(**@system_arguments)) { content }
+ render(Primer::LinkComponent.new(href: @href, classes: @system_arguments[:classes])) do
+ render(Primer::BaseComponent.new(**@system_arguments.except(:classes))) { content }
end
else
render(Primer::BaseComponent.new(classes: @classes, **@system_arguments)) { content }


message: Add a test to assert the value of an AvatarComponent's link wrapper

diff:
@@ -50,9 +50,10 @@ class PrimerAvatarComponentTest < Minitest::Test
end
def test_renders_link_wrapper
- render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#"))
+ render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#given-href"))
- assert_selector("a.avatar") do
+ assert_selector("a.avatar") do |(a)|
+ assert_equal("#given-href", a["href"])
assert_selector("img")
end
refute_selector("img.avatar")


message: Remove dead code and test custom classes on bare AvatarComponents

diff:
@@ -33,6 +33,7 @@ module Primer
@system_arguments[:size] = size
@system_arguments[:height] = size
@system_arguments[:width] = size
+
@system_arguments[:classes] = class_names(
system_arguments[:classes],
"avatar",
@@ -47,7 +48,7 @@ module Primer
render(Primer::BaseComponent.new(**@system_arguments.except(:classes))) { content }
end
else
- render(Primer::BaseComponent.new(classes: @classes, **@system_arguments)) { content }
+ render(Primer::BaseComponent.new(**@system_arguments)) { content }
end
end
end

diff:
@@ -36,6 +36,12 @@ class PrimerAvatarComponentTest < Minitest::Test
refute_selector(".avatar--small")
end
+ def test_adds_custom_classes
+ render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", classes: "custom-class"))
+
+ assert_selector("img.avatar.custom-class")
+ end
+
def test_sets_size_height_and_width
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", size: 50))


message: Apply avatar classes to both the image and its link wrapper

diff:
@@ -24,31 +24,33 @@ module Primer
# @param square [Boolean] Used to create a square avatar.
# @param href [String] The URL to link to. If used, component will be wrapped by an `<a>` tag.
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
- def initialize(src:, alt:, size: 20, square: false, href: nil, **system_arguments)
+ def initialize(src:, alt:, size: 20, square: false, href: nil, classes: nil, **image_arguments)
@href = href
- @system_arguments = system_arguments
- @system_arguments[:tag] = :img
- @system_arguments[:src] = src
- @system_arguments[:alt] = alt
- @system_arguments[:size] = size
- @system_arguments[:height] = size
- @system_arguments[:width] = size
- @system_arguments[:classes] = class_names(
- system_arguments[:classes],
+ @image_arguments = image_arguments
+ @image_arguments[:tag] = :img
+ @image_arguments[:src] = src
+ @image_arguments[:alt] = alt
+ @image_arguments[:size] = size
+ @image_arguments[:height] = size
+ @image_arguments[:width] = size
+
+ @avatar_classes = class_names(
"avatar",
"avatar--small" => size < SMALL_THRESHOLD,
"circle" => !square
)
+
+ @all_classes = class_names(classes, @avatar_classes)
end
def call
if @href
- render(Primer::LinkComponent.new(href: @href, classes: @system_arguments[:classes])) do
- render(Primer::BaseComponent.new(**@system_arguments.except(:classes))) { content }
+ render(Primer::LinkComponent.new(href: @href, classes: @all_classes)) do
+ render(Primer::BaseComponent.new(classes: @avatar_classes, **@image_arguments)) { content }
end
else
- render(Primer::BaseComponent.new(**@system_arguments)) { content }
+ render(Primer::BaseComponent.new(classes: @all_classes, **@image_arguments)) { content }
end
end
end

diff:
@@ -60,25 +60,23 @@ class PrimerAvatarComponentTest < Minitest::Test
assert_selector("a.avatar") do |(a)|
assert_equal("#given-href", a["href"])
- assert_selector("img")
+ assert_selector("img.avatar")
end
- refute_selector("img.avatar")
end
def test_defaults_circle_link_wrapper
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#"))
assert_selector("a.avatar.circle") do
- assert_selector("img")
+ assert_selector("img.avatar.circle")
end
- refute_selector("img.avatar")
end
def test_squared_link_wrapper
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", square: true))
assert_selector("a.avatar") do
- assert_selector("img")
+ assert_selector("img.avatar")
end
refute_selector(".circle")
end
@@ -87,16 +85,15 @@ class PrimerAvatarComponentTest < Minitest::Test
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", size: Primer::AvatarComponent::SMALL_THRESHOLD - 1))
assert_selector("a.avatar.avatar--small") do
- assert_selector("img")
+ assert_selector("img.avatar.avatar--small")
end
- refute_selector("img.avatar")
end
def test_does_not_add_small_modifier_to_link_wrapper_when_size_is_greater_than_threshold
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", size: Primer::AvatarComponent::SMALL_THRESHOLD + 1))
assert_selector("a.avatar") do
- assert_selector("img")
+ assert_selector("img.avatar")
end
refute_selector(".avatar--small")
end
@@ -105,7 +102,7 @@ class PrimerAvatarComponentTest < Minitest::Test
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", classes: "custom-class"))
assert_selector("a.avatar.custom-class") do
- assert_selector("img")
+ assert_selector("img.avatar")
end
refute_selector("img.custom-class")
end


message: Fix AvatarComponent to apply the avatar-small class, not avatar--small

diff:
*Steve Richert*
+* Fix `AvatarComponent` to apply the `avatar-small` class rather than `avatar--small`.
+
+ *Steve Richert*
+
## 0.0.27
* Promote `BreadcrumbComponent` and `ProgressBarComponent` to beta status.

diff:
@@ -37,7 +37,7 @@ module Primer
@avatar_classes = class_names(
"avatar",
- "avatar--small" => size < SMALL_THRESHOLD,
+ "avatar-small" => size < SMALL_THRESHOLD,
"circle" => !square
)

diff:
@@ -17,7 +17,7 @@ for organizations or any other non-human avatars.
### Default
-<Example src="" />
+<Example src="" />
```erb
<%= render(Primer::AvatarComponent.new(src: "http://placekitten.com/200/200", alt: "@kittenuser")) %>
@@ -25,7 +25,7 @@ for organizations or any other non-human avatars.
### Square
-<Example src="<img src='http://placekitten.com/200/200' alt='@kittenuser' size='20' height='20' width='20' class='avatar avatar--small '></img>" />
+<Example src="<img src='http://placekitten.com/200/200' alt='@kittenuser' size='20' height='20' width='20' class='avatar avatar-small '></img>" />
```erb
<%= render(Primer::AvatarComponent.new(src: "http://placekitten.com/200/200", alt: "@kittenuser", square: true)) %>
@@ -33,7 +33,7 @@ for organizations or any other non-human avatars.
### Link
-<Example src="" />
+<Example src="" />
```erb
<%= render(Primer::AvatarComponent.new(href: "#", src: "http://placekitten.com/200/200", alt: "@kittenuser")) %>

diff:
@@ -15,7 +15,7 @@ Use AvatarStack to stack multiple avatars together.
### Default
-<Example src="

" />
+<Example src="

" />
```erb
<%= render(Primer::AvatarStackComponent.new) do |c| %>
@@ -27,7 +27,7 @@ Use AvatarStack to stack multiple avatars together.
### Align right
-<Example src="<div class='AvatarStack AvatarStack--right AvatarStack--three-plus '> <div class='AvatarStack-body '> <img src='http://placekitten.com/200/200' alt='@kittenuser' size='20' height='20' width='20' class='avatar avatar--small circle '></img> <img src='http://placekitten.com/200/200' alt='@kittenuser' size='20' height='20' width='20' class='avatar avatar--small circle '></img> <div class='avatar avatar-more'></div> <img src='http://placekitten.com/200/200' alt='@kittenuser' size='20' height='20' width='20' class='avatar avatar--small circle '></img></div></div>" />
+<Example src="<div class='AvatarStack AvatarStack--right AvatarStack--three-plus '> <div class='AvatarStack-body '> <img src='http://placekitten.com/200/200' alt='@kittenuser' size='20' height='20' width='20' class='avatar avatar-small circle '></img> <img src='http://placekitten.com/200/200' alt='@kittenuser' size='20' height='20' width='20' class='avatar avatar-small circle '></img> <div class='avatar avatar-more'></div> <img src='http://placekitten.com/200/200' alt='@kittenuser' size='20' height='20' width='20' class='avatar avatar-small circle '></img></div></div>" />
```erb
<%= render(Primer::AvatarStackComponent.new(align: :right)) do |c| %>
@@ -39,7 +39,7 @@ Use AvatarStack to stack multiple avatars together.
### With tooltip
-<Example src="

" />
+<Example src="

" />
```erb
<%= render(Primer::AvatarStackComponent.new(tooltipped: true, body_arguments: { label: 'This is a tooltip!' })) do |c| %>

diff:
@@ -26,14 +26,14 @@ class PrimerAvatarComponentTest < Minitest::Test
def test_defaults_adds_small_modifier_when_size_is_less_than_threshold
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", size: Primer::AvatarComponent::SMALL_THRESHOLD - 1))
- assert_selector("img.avatar.avatar--small")
+ assert_selector("img.avatar.avatar-small")
end
def test_defaults_does_not_add_small_modifier_when_size_is_greater_than_threshold
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", size: Primer::AvatarComponent::SMALL_THRESHOLD + 1))
assert_selector("img.avatar")
- refute_selector(".avatar--small")
+ refute_selector(".avatar-small")
end
def test_adds_custom_classes
@@ -84,8 +84,8 @@ class PrimerAvatarComponentTest < Minitest::Test
def test_adds_small_modifier_to_link_wrapper_when_size_is_less_than_threshold
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", size: Primer::AvatarComponent::SMALL_THRESHOLD - 1))
- assert_selector("a.avatar.avatar--small") do
- assert_selector("img.avatar.avatar--small")
+ assert_selector("a.avatar.avatar-small") do
+ assert_selector("img.avatar.avatar-small")
end
end
@@ -95,7 +95,7 @@ class PrimerAvatarComponentTest < Minitest::Test
assert_selector("a.avatar") do
assert_selector("img.avatar")
end
- refute_selector(".avatar--small")
+ refute_selector(".avatar-small")
end
def test_adds_custom_classes_to_link_wrapper


message: Move breaking changes back to the bottom of the main changelog section

diff:
*Keith Cirkel* & *Clay Miller*
-* **Breaking change**: Updates `PopoverComponent` to use Slots V2.
-
- *Manuel Puyol*
-
* Fix `AvatarComponent` to apply classes to the link wrapper if present.
*Steve Richert*
*Steve Richert*
+* **Breaking change**: Updates `PopoverComponent` to use Slots V2.
+
+ *Manuel Puyol*
+
## 0.0.27
* Promote `BreadcrumbComponent` and `ProgressBarComponent` to beta status.


message: Add a "story" for a linked avatar stack

diff:
@@ -39,4 +39,16 @@ class Primer::AvatarStackComponentStories < ViewComponent::Storybook::Stories
component.avatar(src: "https://github.com/github.png", alt: "github")
end
end
+
+ story(:linked) do
+ controls do
+ select(:align, Primer::AvatarStackComponent::ALIGN_OPTIONS, Primer::AvatarStackComponent::ALIGN_DEFAULT)
+ end
+
+ content do |component|
+ component.avatar(href: "#", src: "https://github.com/github.png", alt: "github")
+ component.avatar(href: "#", src: "https://github.com/github.png", alt: "github")
+ component.avatar(href: "#", src: "https://github.com/github.png", alt: "github")
+ end
+ end
end

original_message: Add a "story" for a linked avatar stack


message: Set avatar link wrapper line height to fix an overflow bug

diff:
@@ -24,33 +24,32 @@ module Primer
# @param square [Boolean] Used to create a square avatar.
# @param href [String] The URL to link to. If used, component will be wrapped by an `<a>` tag.
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
- def initialize(src:, alt:, size: 20, square: false, href: nil, classes: nil, **image_arguments)
+ def initialize(src:, alt:, size: 20, square: false, href: nil, **system_arguments)
@href = href
+ @system_arguments = system_arguments
+ @system_arguments[:tag] = :img
+ @system_arguments[:src] = src
+ @system_arguments[:alt] = alt
+ @system_arguments[:size] = size
+ @system_arguments[:height] = size
+ @system_arguments[:width] = size
- @image_arguments = image_arguments
- @image_arguments[:tag] = :img
- @image_arguments[:src] = src
- @image_arguments[:alt] = alt
- @image_arguments[:size] = size
- @image_arguments[:height] = size
- @image_arguments[:width] = size
-
- @avatar_classes = class_names(
+ @system_arguments[:classes] = class_names(
+ system_arguments[:classes],
"avatar",
"avatar-small" => size < SMALL_THRESHOLD,
- "circle" => !square
+ "circle" => !square,
+ "lh-0" => !!href # Addresses a overflow issue with linked avatars
)
-
- @all_classes = class_names(classes, @avatar_classes)
end
def call
if @href
- render(Primer::LinkComponent.new(href: @href, classes: @all_classes)) do
- render(Primer::BaseComponent.new(classes: @avatar_classes, **@image_arguments)) { content }
+ render(Primer::LinkComponent.new(href: @href, classes: @system_arguments[:classes])) do
+ render(Primer::BaseComponent.new(**@system_arguments.except(:classes))) { content }
end
else
- render(Primer::BaseComponent.new(classes: @all_classes, **@image_arguments)) { content }
+ render(Primer::BaseComponent.new(**@system_arguments)) { content }
end
end
end

diff:
@@ -33,7 +33,7 @@ for organizations or any other non-human avatars.
### Link
-<Example src="" />
+<Example src="" />
```erb
<%= render(Primer::AvatarComponent.new(href: "#", src: "http://placekitten.com/200/200", alt: "@kittenuser")) %>

diff:
@@ -60,7 +60,8 @@ class PrimerAvatarComponentTest < Minitest::Test
assert_selector("a.avatar") do |(a)|
assert_equal("#given-href", a["href"])
- assert_selector("img.avatar")
+ assert_selector("img")
+ refute_selector("img.avatar")
end
end
@@ -68,7 +69,8 @@ class PrimerAvatarComponentTest < Minitest::Test
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#"))
assert_selector("a.avatar.circle") do
- assert_selector("img.avatar.circle")
+ assert_selector("img")
+ refute_selector("img.circle")
end
end
@@ -76,7 +78,7 @@ class PrimerAvatarComponentTest < Minitest::Test
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", square: true))
assert_selector("a.avatar") do
- assert_selector("img.avatar")
+ assert_selector("img")
end
refute_selector(".circle")
end
@@ -85,7 +87,8 @@ class PrimerAvatarComponentTest < Minitest::Test
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", size: Primer::AvatarComponent::SMALL_THRESHOLD - 1))
assert_selector("a.avatar.avatar-small") do
- assert_selector("img.avatar.avatar-small")
+ assert_selector("img")
+ refute_selector("img.avatar-small")
end
end
@@ -93,7 +96,7 @@ class PrimerAvatarComponentTest < Minitest::Test
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", size: Primer::AvatarComponent::SMALL_THRESHOLD + 1))
assert_selector("a.avatar") do
- assert_selector("img.avatar")
+ assert_selector("img")
end
refute_selector(".avatar-small")
end
@@ -102,10 +105,19 @@ class PrimerAvatarComponentTest < Minitest::Test
render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#", classes: "custom-class"))
assert_selector("a.avatar.custom-class") do
- assert_selector("img.avatar")
- end
+ assert_selector("img")
refute_selector("img.custom-class")
end
+ end
+
+ def test_clears_link_wrapper_line_height
+ render_inline(Primer::AvatarComponent.new(src: "https://github.com/github.png", alt: "github", href: "#"))
+
+ assert_selector("a.lh-0") do
+ assert_selector("img")
+ refute_selector("img.lh-0")
+ end
+ end
def test_status
assert_component_state(Primer::AvatarComponent, :beta)


message:
Appease the linter by avoiding double negation
The class_names method only checks truthiness.

diff:
@@ -39,7 +39,7 @@ module Primer
"avatar",
"avatar-small" => size < SMALL_THRESHOLD,
"circle" => !square,
- "lh-0" => !!href # Addresses an overflow issue with linked avatars
+ "lh-0" => href # Addresses an overflow issue with linked avatars
)
end


message:
Fix Primer::ButtonComponent rendering with a summary tag
Previously, the "type" attribute was being applied to "summary" buttons,
which is invalid and broke visual styling in Safari.

diff:
@@ -55,10 +55,10 @@ module Primer
@system_arguments = system_arguments
@system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, DEFAULT_TAG)
- if @system_arguments[:tag] == :a
- @system_arguments[:role] = :button
- else
+ if @system_arguments[:tag] == :button
@system_arguments[:type] = type
+ else
+ @system_arguments[:role] = :button
end
@system_arguments[:classes] = class_names(

diff:
@@ -21,6 +21,14 @@ class PrimerButtonComponentTest < Minitest::Test
render_inline(Primer::ButtonComponent.new(tag: :a)) { "content" }
assert_selector("a.btn[role='button']")
+ refute_selector("a[type]")
+ end
+
+ def test_renders_summary_as_a_button
+ render_inline(Primer::ButtonComponent.new(tag: :summary)) { "content" }
+
+ assert_selector("summary.btn[role='button']")
+ refute_selector("summary[type]")
end
def test_renders_href