Skip to content

Commit

Permalink
Merge pull request #2011 from cardstack/cs-7513-create-colorpalette-b…
Browse files Browse the repository at this point in the history
…oxel-ui-component

Add Boxel UI components: ColorPicker, ColorPalette
  • Loading branch information
jurgenwerk authored Jan 9, 2025
2 parents b2f4daf + 952b33c commit c646313
Show file tree
Hide file tree
Showing 6 changed files with 338 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/boxel-ui/addon/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import CardContainer from './components/card-container/index.gts';
import CardContentContainer from './components/card-content-container/index.gts';
import CardHeader from './components/card-header/index.gts';
import CircleSpinner from './components/circle-spinner/index.gts';
import ColorPalette from './components/color-palette/index.gts';
import ColorPicker from './components/color-picker/index.gts';
import DateRangePicker from './components/date-range-picker/index.gts';
import DndKanbanBoard, {
type DndItem,
Expand Down Expand Up @@ -78,6 +80,8 @@ export {
CardContentContainer,
CardHeader,
CircleSpinner,
ColorPalette,
ColorPicker,
DateRangePicker,
DndColumn,
DndItem,
Expand Down
148 changes: 148 additions & 0 deletions packages/boxel-ui/addon/src/components/color-palette/index.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { eq } from '@cardstack/boxel-ui/helpers';
import { concat, fn } from '@ember/helper';
import { on } from '@ember/modifier';
import { htmlSafe } from '@ember/template';
import Component from '@glimmer/component';

import ColorPicker from '../color-picker/index.gts';

interface Signature {
Args: {
color: string;
onChange: (color: string) => void;
};
Element: HTMLDivElement;
}

const DEFAULT_PALETTE_COLORS = [
// Row 1
'#000000',
'#777777',
'#FA2200',
'#FA7F01',
'#FBEB06',
'#1EDF67',
'#39B1FF',
'#9D00FF',
// Row 2
'#A6A6A6',
'#CFCFCF',
'#FCA6A7',
'#FCD2A7',
'#FCF8A6',
'#A6F4CA',
'#A7E4FF',
'#DEA6FF',
];

export default class ColorPalette extends Component<Signature> {
colors = DEFAULT_PALETTE_COLORS;

<template>
<div class='color-palette-container' ...attributes>
<div class='color-palette'>
{{#each this.colors as |color|}}
<button
type='button'
class='swatch {{if (eq color @color) "selected"}}'
style={{htmlSafe (concat '--swatch-color: ' color)}}
{{on 'click' (fn @onChange color)}}
title={{color}}
/>
{{/each}}
</div>

<label class='color-picker-container'>
<span class='custom-color-label'>Custom Color</span>
<ColorPicker @color={{@color}} @onChange={{@onChange}} />
</label>
</div>

<style scoped>
.custom-color-label {
margin-left: var(--boxel-sp-sm);
color: var(--boxel-450);
}
.color-palette-container {
display: flex;
gap: var(--boxel-sp);
align-items: flex-start;
flex-direction: column;
}
.color-picker-container {
--swatch-size: 1.8rem;
border: 1px solid var(--boxel-border-color);
border-radius: var(--boxel-border-radius);
padding: var(--boxel-sp-sm);
background: none;
display: flex;
align-items: center;
cursor: pointer;
flex-direction: row-reverse;
width: 18rem;
justify-content: flex-end;
}
.color-picker-container:hover {
background-color: var(--boxel-light-100);
color: var(--boxel-600);
}
.color-palette {
--swatch-size: 1.8rem;
display: grid;
grid-template-columns: repeat(8, var(--swatch-size));
gap: var(--boxel-sp-xs);
}
.swatch {
width: var(--swatch-size);
height: var(--swatch-size);
border: 1px solid transparent;
border-radius: 50%;
padding: 2px;
cursor: pointer;
transition: transform 0.1s ease;
background-color: transparent;
}
.swatch::before {
content: '';
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: var(--swatch-color);
}
.swatch:hover:not(:disabled) {
transform: scale(1.1);
}
.swatch.selected {
background-color: white;
border-color: var(--boxel-800);
}
.color-input {
width: 1.35rem;
height: 1.35rem;
padding: 0;
border: none;
cursor: pointer;
border-radius: 50%;
}
.color-input::-webkit-color-swatch-wrapper {
padding: 0;
}
.color-input::-webkit-color-swatch {
border: 1px solid transparent;
border-radius: 50%;
}
</style>
</template>
}
45 changes: 45 additions & 0 deletions packages/boxel-ui/addon/src/components/color-palette/usage.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { fn } from '@ember/helper';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import FreestyleUsage from 'ember-freestyle/components/freestyle/usage';

import ColorPalette from './index.gts';

export default class ColorPaletteUsage extends Component {
@tracked color = '#000000';

private handleColorChange = (newColor: string) => {
this.color = newColor;
};

<template>
<FreestyleUsage
@name='ColorPalette'
@description='A color palette component that provides a set of predefined colors and a custom color picker.'
>
<:example>
<ColorPalette
@color={{this.color}}
@onChange={{this.handleColorChange}}
/>
</:example>

<:api as |Args|>
<Args.String
@name='color'
@optional={{false}}
@description='Currently selected color in hex format.'
@value={{this.color}}
@onInput={{fn (mut this.color)}}
@defaultValue='#000000'
/>
<Args.Action
@name='onChange'
@description='Callback function that receives the newly selected color value.'
@value={{this.handleColorChange}}
@onInput={{fn (mut this.handleColorChange)}}
/>
</:api>
</FreestyleUsage>
</template>
}
74 changes: 74 additions & 0 deletions packages/boxel-ui/addon/src/components/color-picker/index.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { on } from '@ember/modifier';
import Component from '@glimmer/component';

interface Signature {
Args: {
color: string;
disabled?: boolean;
onChange: (color: string) => void;
showHexString?: boolean;
};
Element: HTMLDivElement;
}

export default class ColorPicker extends Component<Signature> {
private handleColorChange = (event: Event) => {
let input = event.target as HTMLInputElement;
this.args.onChange(input.value);
};

<template>
<div class='color-picker' ...attributes>
<input
type='color'
value={{@color}}
class='input'
disabled={{@disabled}}
aria-label='Choose color'
{{on 'input' this.handleColorChange}}
/>
{{#if @showHexString}}
<span class='hex-value'>{{@color}}</span>
{{/if}}
</div>

<style scoped>
.color-picker {
--swatch-size: 1.4rem;
display: inline-flex;
align-items: center;
gap: var(--boxel-sp-xs);
}
.input {
width: var(--swatch-size);
height: var(--swatch-size);
padding: 0;
border: none;
cursor: pointer;
background: transparent;
border: 1px solid var(--boxel-200);
border-radius: 50%;
}
.input:disabled {
pointer-events: none;
}
.input::-webkit-color-swatch-wrapper {
padding: 0;
}
.input::-webkit-color-swatch {
border: 1px solid transparent;
border-radius: 50%;
}
.hex-value {
font: var(--boxel-font);
color: var(--boxel-dark);
text-transform: uppercase;
}
</style>
</template>
}
63 changes: 63 additions & 0 deletions packages/boxel-ui/addon/src/components/color-picker/usage.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { fn } from '@ember/helper';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import FreestyleUsage from 'ember-freestyle/components/freestyle/usage';

import ColorPicker from './index.gts';

export default class ColorPickerUsage extends Component {
@tracked color = '#ff0000';
@tracked disabled = false;
@tracked showHexString = true;

private onChange = (newColor: string) => {
this.color = newColor;
};

<template>
<FreestyleUsage
@name='ColorPicker'
@description='A color picker that allows users to select a color from the color spectrum.'
>
<:example>
<ColorPicker
@color={{this.color}}
@onChange={{this.onChange}}
@showHexString={{this.showHexString}}
@disabled={{this.disabled}}
/>
</:example>

<:api as |Args|>
<Args.String
@name='color'
@optional={{false}}
@description='Hex color value.'
@value={{this.color}}
@onInput={{fn (mut this.color)}}
@defaultValue='#ff0000'
/>
<Args.Action
@name='onChange'
@description='A callback function that is called when the color is changed.'
@value={{this.onChange}}
@onInput={{fn (mut this.onChange)}}
/>
<Args.Bool
@name='disabled'
@description='Whether the color picker is disabled.'
@value={{this.disabled}}
@onInput={{fn (mut this.disabled)}}
@defaultValue={{false}}
/>
<Args.Bool
@name='showHexString'
@description='Whether to show the hex color value next to the picker.'
@value={{this.showHexString}}
@onInput={{fn (mut this.showHexString)}}
@defaultValue={{true}}
/>
</:api>
</FreestyleUsage>
</template>
}
4 changes: 4 additions & 0 deletions packages/boxel-ui/addon/src/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import SwitchUsage from './components/switch/usage.gts';
import TabbedHeaderUsage from './components/tabbed-header/usage.gts';
import TooltipUsage from './components/tooltip/usage.gts';
import ViewSelectorUsage from './components/view-selector/usage.gts';
import ColorPickerUsage from './components/color-picker/usage.gts';
import ColorPaletteUsage from './components/color-palette/usage.gts';

export const ALL_USAGE_COMPONENTS = [
['Accordion', AccordionUsage],
Expand Down Expand Up @@ -77,4 +79,6 @@ export const ALL_USAGE_COMPONENTS = [
['TabbedHeader', TabbedHeaderUsage],
['Tooltip', TooltipUsage],
['ViewSelector', ViewSelectorUsage],
['ColorPicker', ColorPickerUsage],
['ColorPalette', ColorPaletteUsage],
];

0 comments on commit c646313

Please sign in to comment.