Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ColorPicker] Add hex/alpha text fields #2186

Closed
wants to merge 23 commits into from

Conversation

ruairiphackett
Copy link
Contributor

@ruairiphackett ruairiphackett commented Sep 24, 2019

WHY are these changes introduced?

Fixes https://github.com/Shopify/polaris-ux/issues/276

WHAT is this pull request doing?

  • Add labels
  • Add i18n for error messages
  • Clean up AlphaField state
  • Add ability to enter english colour names into the 'hex' field
  • Add tests
  • Request translations once strings are reviewed

How to 🎩

🖥 Local development instructions
🗒 General tophatting guidelines
📄 Changelog guidelines

Copy-paste this code in playground/Playground.tsx:
import React from 'react';
import {Page} from '../src';

export function Playground() {
  return (
    <Page title="Playground">
      {/* Add the code you want to test in here */}
    </Page>
  );
}

🎩 checklist

@github-actions
Copy link
Contributor

github-actions bot commented Oct 3, 2019

🔴 This pull request modifies 16 files and might impact 71 other files. Because this is a larger than average splash zone for a change, remember to tophat areas that could be affected.

Details:
All files potentially affected (total: 71)
📄 UNRELEASED.md (total: 0)

Files potentially affected (total: 0)

📄 locales/en.json (total: 0)

Files potentially affected (total: 0)

🎨 src/components/ColorPicker/ColorPicker.scss (total: 1)

Files potentially affected (total: 1)

🧩 src/components/ColorPicker/ColorPicker.tsx (total: 0)

Files potentially affected (total: 0)

🧩 src/components/ColorPicker/components/AlphaField/AlphaField.tsx (total: 1)

Files potentially affected (total: 1)

🧩 src/components/ColorPicker/components/AlphaField/index.ts (total: 1)

Files potentially affected (total: 1)

🧩 src/components/ColorPicker/components/AlphaField/tests/AlphaField.test.tsx (total: 0)

Files potentially affected (total: 0)

🧩 src/components/ColorPicker/components/TextPicker/TextPicker.tsx (total: 1)

Files potentially affected (total: 1)

🧩 src/components/ColorPicker/components/TextPicker/index.ts (total: 1)

Files potentially affected (total: 1)

🧩 src/components/ColorPicker/components/TextPicker/tests/TextPicker.test.tsx (total: 0)

Files potentially affected (total: 0)

🧩 src/components/ColorPicker/components/index.ts (total: 1)

Files potentially affected (total: 1)

🧩 src/components/ColorPicker/tests/ColorPicker.test.tsx (total: 0)

Files potentially affected (total: 0)

🧩 src/utilities/color-map.ts (total: 71)

Files potentially affected (total: 71)

🧩 src/utilities/color-transformers.ts (total: 70)

Files potentially affected (total: 70)

🧩 src/utilities/color-validation.ts (total: 68)

Files potentially affected (total: 68)

🧩 src/utilities/tests/color-transformers.test.ts (total: 0)

Files potentially affected (total: 0)

@tleunen
Copy link
Contributor

tleunen commented Oct 3, 2019

  • Should the hex color value contain the opacity? (becoming a 8 digits hex instead of 6 when the alpha is enabled)
  • Should the onChange callback return the hex value alongside its params? Or should the consumer find out a way to convert the hue, brithness, saturation and alpha into an rgba/hex? In most cases, I'd guess the consumer just wants a color value?

Copy link
Member

@AndrewMusgrave AndrewMusgrave left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the work you've put in, it's looking great, we Polaris really appreciates it 😄

I've noticed your adding props onto state, it's an anti-pattern in react. Converting the components to a functional component should help avoid this 😄

Adding some tests would be awesome 😄 🙏

package.json Outdated
@@ -192,6 +192,7 @@
"@types/react-transition-group": "^2.0.7",
"hoist-non-react-statics": "^3.3.0",
"lodash": "^4.17.4",
"tinycolor2": "^1.4.1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to add this package?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'll remove it and add the svg colour map 👍

@@ -1,5 +1,9 @@
import {names} from 'tinycolor2';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the only reason we're using tinycolor2 we should probably just include the map ourselves rather than adding a package for it.

import {RGBColor, RGBAColor} from './color-types';

export const nameHexMap: Record<string, string> = names;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would wonder if we really need keyword support though, what was the reason for adding it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This came up as part of a conversation with @dpersing about what aspects of the online-store-web implementation to carry over.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the idea that online-store-web will consume our color picker?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of the change is to make the ColorPicker accessible by adding keyboard support. There's no plan for any particular team to consume the new version, but hopefully it'll be used in favour of the old one.

Copy link
Member

@AndrewMusgrave AndrewMusgrave Oct 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I maybe under an incorrect assumption but I suspect the majority of people don't know color keywords, apart from the basic; red, green, purple. Not the rebeccapurple, forestgreen, papayawhip, etc. Is this something being requested accessibility wise? @dpersing

I'm not saying we shouldn't have it, just curious about the context before we increase our bundle 😄

import {RGBColor, RGBAColor} from './color-types';

export const nameHexMap: Record<string, string> = names;
const IS_RGB_STRING_REGEX = /rgba?\(((25[0-5]|2[0-4]\d|1\d{1,2}|\d\d?)\s*,\s*?){2}(25[0-5]|2[0-4]\d|1\d{1,2}|\d\d?)\s*,?\s*([01]\.?\d*?)?\)/i;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not to common but this regex won't catch all RGBA values

rgba( 0255,255,255, 01);

is a valid RGB but would fail

}

export function isRgbString(value: string) {
return IS_RGB_STRING_REGEX.test(value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this regex stored as a constant and the others weren't?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had been using it elsewhere & removed the other uses, good catch.

};
}

// Use named export once withAppProvider is refactored away
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're in the process of removing all class components / withAppProvider. Could we convert this to a functional component and use the useI18n hook?

Comment on lines 77 to 81
let roundedValue = Math.round(percentage);

if (roundedValue !== null) {
if (roundedValue > 100) {
roundedValue = 100;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be easier to do something like

const roundedValue = clamp(Math.round(percentage), 1, 100);

Or something along those lines, what do you think?

const {color} = props;

const style = {backgroundColor: hsbToString(color)};
return <div style={style} className={styles.SwatchBackground} />;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why this is a component rather than inline?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can be inlined looking back at it 👍

I'll make the necessary changes

};
}

// Use named export once withAppProvider is refactored away
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above 😄

type CombinedProps = TextPickerProps & WithAppProviderProps;

interface State {
valueHasBeenUpdatedOnce: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably have this as a class property rather than state unless we needed it to trigger a re-render?

@ruairiphackett
Copy link
Contributor Author

@AndrewMusgrave @tleunen thanks a million for looking at this for me!

Should the hex color value contain the opacity? (becoming a 8 digits hex instead of 6 when the alpha is enabled)

There were a few questions relating to 6/8 digit hex, so I thought I'd bring the discussion to one place.

We went with 6 digit hex to match what is currently used in both the rails and react implementations of the colour picker in the online store (specifically the one used in the theme editor).

Do you think it'd be better to use a 8 digit version?

@mirualves @dpersing Any thoughts on switching to 8 digit hex that would include the alpha value (representing RGBA, instead of the 6 digit hex that represents RGB) or should we keep the 2 separate as 6 digit hex and alpha percentage as they are in the designs currently?

@dpersing
Copy link
Contributor

dpersing commented Oct 4, 2019

or should we keep the 2 separate as 6 digit hex and alpha percentage as they are in the designs currently?

I'm in favor of keeping them separate. I don't think we want to assume that most merchants will be comfortable enough with CSS to know how RGBA works. In this case I think we want to do the work of parsing what most merchants will understand, versus what would be most efficient.

@ruairiphackett @mirualves @AndrewMusgrave

@AndrewMusgrave
Copy link
Member

Sorry I didn't realize this was ready for review again, re-requesting myself so I'll get to it later 😄

@AndrewMusgrave AndrewMusgrave self-requested a review October 8, 2019 15:30
@AndrewMusgrave
Copy link
Member

or should we keep the 2 separate as 6 digit hex and alpha percentage as they are in the designs currently?

I'm in favor of keeping them separate. 8 digit hex codes aren't too common among developers from what I've seen so I image merchants without a strong understanding of CSS or color systems may be thrown off.

@AndrewMusgrave
Copy link
Member

Feel free to ping me when this is ready for a review again 😄

@ruairiphackett
Copy link
Contributor Author

@AndrewMusgrave It isn't quite ready for review yet, getting there 😄

I'll ping you when it's ready. I also want to add tests before the next one 👍

@ruairiphackett ruairiphackett force-pushed the colorpicker-hex-editor branch from ff79ec5 to 0ab5a78 Compare October 9, 2019 13:20
@ruairiphackett ruairiphackett changed the title [WIP] [ColorPicker] Add hex/alpha text fields [ColorPicker] Add hex/alpha text fields Oct 11, 2019
@ruairiphackett
Copy link
Contributor Author

@AndrewMusgrave This is ready for another review if you get a chance ❤️

I changed the 2 subcomponents to functional components & hopefully addressed all the other review comments. I also added tests, but let me know if they don't cover enough 👍

@ruairiphackett
Copy link
Contributor Author

ruairiphackett commented Oct 11, 2019

@mirualves @dpersing

Apologies for the delay in working on the ColorPicker, I've had other projects on the go at the same time, so it's been a bit slow going!

This PR is ready for its second code review (@AndrewMusgrave gave a first preliminary one last week).

It adds the text colour input field (TextPicker) and alpha input field (AlphaField):

https://screenshot.click/11-51-096ou-vcdp2.png

I implemented the same valid colour inputs as were used in the Online Store implementation, but was wondering if we could extend it to accept more colour types as input?

Currently accepted colour types

3 & 6 digit hex codes:

  • #000000
  • 000000
  • #000
  • 000

rgb colours:

  • rgb(0,0,0)

Colour names:

To illustrate its usage, here's a quick video:

https://screenshot.click/11-27-3tqli-mcdca.mp4

Adding more colour types?

I was wondering if we should add to the valid colour types for the TextPicker, by adding support for:

  • hsl(0, 100%, 50%)
  • hsb(0, 100%, 50%)

Also, we could make the TextPicker accept colour types that set the alpha value of the colour by adding support for the following colour types:

  • rgba(0, 255, 124, 0.5)
  • hsla(0, 100%, 50%, 0.5)

Let me know if you have any thoughts on extending it like this or any other comments 👍

@dpersing
Copy link
Contributor

dpersing commented Oct 11, 2019

@ruairiphackett I'm thinking for this version to keep the current support, and perhaps split out the extended support into a separate PR for a later date. What do you think, @mirualves?

Also, it looks great.

@mirualves
Copy link
Contributor

I'm thinking for this version to keep the current support, and perhaps split out the extended support into a separate PR for a later date.

By current support do you mean hex + RGB? I'll only make sure the input field accommodates the 3 digits for each RGB value, like rgb(196,196,196)

It really looks great, @ruairiphackett!

@ruairiphackett
Copy link
Contributor Author

By current support do you mean hex + RGB? I'll only make sure the input field accommodates the 3 digits for each RGB value, like rgb(196,196,196)

@mirualves any rgb value will currently work, so rgb(196,196,196) currently works 👍

I'm thinking for this version to keep the current support, and perhaps split out the extended support into a separate PR for a later date.

@dpersing I'll create an issue to track this and the extended support can either be incorporated here or in a later PR depending on what is decided.

@mirualves
Copy link
Contributor

mirualves commented Oct 21, 2019

any rgb value will currently work, so rgb(196,196,196) currently works

Awesome! @ruairiphackett, to be clear, I'm referring to the characters inside the input field, without overflowing it:

color-picker

@ruairiphackett
Copy link
Contributor Author

ruairiphackett commented Oct 21, 2019

https://user-images.githubusercontent.com/5349023/67212469-6c840900-f3ea-11e9-8068-6d7d452bf80d.png

@mirualves Apologies, I misunderstood! That's true, when the transparent value is present the text does overflow when entering most rgb values: https://screenshot.click/21-13-o51af-yiwrn.mp4

That behaviour isn't ideal 💯

Could this be affected by the font type/size being used also? If we make the fix above might we run into the same issue in certain instances anyway? Adding spaces, e.g. rgb(255, 255, 255), could also affect things.

@mirualves
Copy link
Contributor

mirualves commented Oct 21, 2019

Could this be affected by the font type/size being used also?

We can fix that by increasing the width of the entire component. By making it a bit wider, it will accommodate the RGB values, no problem.
I think the font size is fine 👍

@ruairiphackett
Copy link
Contributor Author

By making it a bit wider, it will accommodate the RGB values, no problem.

Cool. I wonder if we should try to maintain a square shape for the main colour picker, i.e. width equal to height for this component?

https://screenshot.click/21-33-95i6t-6ibpx.png

@mirualves
Copy link
Contributor

Cool. I wonder if we should try to maintain a square shape for the main colour picker, i.e. width equal to height for this component?

I don't have strong option for it. It seems to work well, regardless if the shape is more squared or not. One argument for keeping the high shorter is to save screen space, but I think we can test both options in context and see how it feels.

See more rectangular examples here: http://casesandberg.github.io/react-color/

"textPickerAccessibilityLabel": "HEX color",
"alphaFieldAccessibilityLabel": "Alpha"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dpersing Any suggestions on what a11y labels should we use here?

The first is used for the hex/colour name/rgb input field and the second is used for the alpha percentage field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added these as placeholders for now.

@ruairiphackett
Copy link
Contributor Author

@AndrewMusgrave I added a change to the component width when the alpha picker is present. This is to allow a full rgb string to be entered into the text field without the text scrolling out of view:

https://screenshot.click/22-01-p1wr7-vg7s7.png

Change:
https://screenshot.click/22-58-abhp2-3j9s9.png

Copy link
Member

@AndrewMusgrave AndrewMusgrave left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding tests, everything is looking great 😍

}

export function isRgbString(value: string) {
return /rgba?\(\s*(0*((25[0-5]|2[0-4]\d|1\d{1,2}|\d\d?)\s*,\s*?)){2}(0*(25[0-5]|2[0-4]\d|1\d{1,2}|\d\d?))\s*,?\s*([01]\.?\d*?)?\s*\)/i.test(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This'll match incorrectly. e.g rgba(0,0,0) without conditionals (like in php) I can't suggest a fix off the top of my head, you'll probably want to look at look behind

Copy link
Contributor Author

@ruairiphackett ruairiphackett Oct 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rgba isn't actually supported as input in this version (it may be added later), so I will change this to only match rgb strings for now.

@@ -9,3 +22,24 @@ export function isLight({red, green, blue}: RGBColor | RGBAColor): boolean {
export function isDark(color: RGBColor | RGBAColor): boolean {
return !isLight(color);
}

export function isColorName(value: string) {
if (nameHexMap[value]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a strict guideline, but we usually use the boolean constructor, i.e

function isColorName(value) { return Boolean(nameHexMap[value]) }

import {RGBColor, RGBAColor} from './color-types';

export const nameHexMap: Record<string, string> = names;
Copy link
Member

@AndrewMusgrave AndrewMusgrave Oct 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I maybe under an incorrect assumption but I suspect the majority of people don't know color keywords, apart from the basic; red, green, purple. Not the rebeccapurple, forestgreen, papayawhip, etc. Is this something being requested accessibility wise? @dpersing

I'm not saying we shouldn't have it, just curious about the context before we increase our bundle 😄

@@ -10,6 +11,8 @@ import {
} from './color-types';
import {compose} from './compose';

const RGB_STRING_TO_HEX_REGEX = /^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as the other regex


export function rgbStringToHex(value: string) {
const rgb = normalizeColorString(value).match(RGB_STRING_TO_HEX_REGEX) || [
'rgb(0,0,0)',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'rgb(0,0,0)',
undefined,

Since we don't use the full capture

return rgbStringToHex(normalizedValue);
case isColorName(normalizedValue):
return nameToHex(normalizedValue);
default:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can omit the default statement and the function will return undefined 😄

const colorValue = 'white';
const expectedHex = nameToHex(normalizeColorString(colorValue));

(textField.find('input') as any).instance().value = colorValue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about getDOMNode and questions about blur

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answer about blur above, will change to getDOMNode.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDOMNode fails without casting, so I will change (this and all other instances) to

(textField.find('input').getDOMNode() as HTMLInputElement).value = colorValue;

https://screenshot.click/07-17-hsngh-0nz7a.png

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference a way to avoid casting here could be to use a type guard. Here's an example using instanceof 😄

const input = textField.find(‘input’).getDOMNode();
const value = input instanceof HTMLElement ? ‘’ : input.value;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Using the ternary operator here gives the further error of
Tests should not contain conditional statements.eslint(jest/no-if)

It also didn't seem to allow me to set the input node's value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AndrewMusgrave I pushed a fix using casting ((textField.find('input').getDOMNode() as HTMLInputElement).value) just to get this to a place where it is ready for another review, I'm not ignoring your advice, don't worry! Just haven't been able to get an error free method to work without casting 🤔

Comment on lines +144 to +183
private handleHexChange = (hex: string) => {
const {
color: {alpha = 1},
onChange,
} = this.props;
const newColor = hexToHsb(hex);
onChange({...newColor, alpha});
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this up to Miru's best judgment 😄

}, 0);
}
}

render() {
const {id, color, allowAlpha} = this.props;
const {hue, saturation, brightness, alpha: providedAlpha} = color;
const {pickerSize} = this.state;
const {pickerWidth, pickerHeight} = this.state;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, do we need to update these values on resize?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, they should be updated 👍

@@ -0,0 +1,95 @@
import React from 'react';
import {mountWithAppProvider} from 'test-utilities/legacy';
import {Spinner} from 'components';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import {Spinner} from 'components';

This should fix CI 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops!

@ruairiphackett
Copy link
Contributor Author

ruairiphackett commented Oct 23, 2019

I maybe under an incorrect assumption but I suspect the majority of people don't know color keywords, apart from the basic; red, green, purple. Not the rebeccapurple, forestgreen, papayawhip, etc. Is this something being requested accessibility wise? @dpersing

I'm not saying we shouldn't have it, just curious about the context before we increase our bundle 😄

@AndrewMusgrave Good point, I'd be up for either reducing the name map (although that might make it difficult for people to figure out what colour names will work) or removing it completely depending on what people think 💯

@ruairiphackett
Copy link
Contributor Author

@AndrewMusgrave Should have those comments addressed now, thanks again

@ruairiphackett ruairiphackett mentioned this pull request May 7, 2020
6 tasks
@AndrewMusgrave AndrewMusgrave removed their request for review June 12, 2020 18:36
@ctrlaltdylan
Copy link

Very interested to see this to completion. Anything I can do to help?

@alex-page alex-page force-pushed the master branch 2 times, most recently from 4eab9e5 to 9e09ba7 Compare January 15, 2021 05:17
Base automatically changed from master to main January 21, 2021 15:38
@alex-page alex-page force-pushed the main branch 7 times, most recently from 2c6e842 to d2611d6 Compare June 11, 2021 16:24
@alex-page
Copy link
Member

@ruairiphackett This PR is excellent and is quite out of date. Are there any smaller pieces you could break apart and ship easily?

@ruairiphackett
Copy link
Contributor Author

This PR is excellent and is quite out of date. Are there any smaller pieces you could break apart and ship easily?

@alex-page I would like to revisit this when I get some time to work on it, hopefully in the new year.

@alex-page
Copy link
Member

@ruairiphackett going to close for now but if you want to keep working on this please reopen.

@alex-page alex-page closed this May 17, 2022
@ruairiphackett
Copy link
Contributor Author

Thanks for the heads up @alex-page

@sagar-ranglani
Copy link

@ruairiphackett This looks very useful... any updates about this?

@ruairiphackett
Copy link
Contributor Author

For anyone who had interest in this PR, it has been updated and recreated in #8294

@sam-b-rose sam-b-rose deleted the colorpicker-hex-editor branch August 24, 2023 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants