@foxui/colors 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/components/color-gradient-picker/ColorGradientPicker.svelte +229 -0
  4. package/dist/components/color-gradient-picker/ColorGradientPicker.svelte.d.ts +16 -0
  5. package/dist/components/color-gradient-picker/index.d.ts +1 -0
  6. package/dist/components/color-gradient-picker/index.js +1 -0
  7. package/dist/components/color-picker/LICENSE +42 -0
  8. package/dist/components/color-picker/base/ColorPicker.svelte +312 -0
  9. package/dist/components/color-picker/base/ColorPicker.svelte.d.ts +23 -0
  10. package/dist/components/color-picker/base/color.d.ts +47 -0
  11. package/dist/components/color-picker/base/color.js +96 -0
  12. package/dist/components/color-picker/base/constants.d.ts +5 -0
  13. package/dist/components/color-picker/base/constants.js +5 -0
  14. package/dist/components/color-picker/base/index.d.ts +3 -0
  15. package/dist/components/color-picker/base/index.js +3 -0
  16. package/dist/components/color-picker/base/render.d.ts +5 -0
  17. package/dist/components/color-picker/base/render.js +77 -0
  18. package/dist/components/color-picker/base/shaders.d.ts +2 -0
  19. package/dist/components/color-picker/base/shaders.js +8 -0
  20. package/dist/components/color-picker/index.d.ts +2 -0
  21. package/dist/components/color-picker/index.js +2 -0
  22. package/dist/components/color-picker/popover/PopoverColorPicker.svelte +75 -0
  23. package/dist/components/color-picker/popover/PopoverColorPicker.svelte.d.ts +26 -0
  24. package/dist/components/color-select/ColorSelect.svelte +82 -0
  25. package/dist/components/color-select/ColorSelect.svelte.d.ts +17 -0
  26. package/dist/components/color-select/index.d.ts +1 -0
  27. package/dist/components/color-select/index.js +1 -0
  28. package/dist/components/index.d.ts +5 -0
  29. package/dist/components/index.js +5 -0
  30. package/dist/components/select-theme/SelectTheme.svelte +124 -0
  31. package/dist/components/select-theme/SelectTheme.svelte.d.ts +10 -0
  32. package/dist/components/select-theme/SelectThemePopover.svelte +49 -0
  33. package/dist/components/select-theme/SelectThemePopover.svelte.d.ts +10 -0
  34. package/dist/components/select-theme/index.d.ts +2 -0
  35. package/dist/components/select-theme/index.js +2 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +2 -0
  38. package/dist/types.d.ts +1 -0
  39. package/package.json +76 -0
@@ -0,0 +1,47 @@
1
+ import { convert, OKHSL, OKHSV, OKLab, OKLCH, sRGB, deserialize, listColorSpaces } from '@texel/color';
2
+ export type Color = string | RGB | OKlab | OKhsv | OKlch;
3
+ export type SetColorFunction = (color: Color) => void;
4
+ export interface RGB {
5
+ r: number;
6
+ g: number;
7
+ b: number;
8
+ }
9
+ export interface OKlab {
10
+ l: number;
11
+ a: number;
12
+ b: number;
13
+ }
14
+ export interface OKhsv {
15
+ h: number;
16
+ s: number;
17
+ v: number;
18
+ }
19
+ export interface OKhsl {
20
+ h: number;
21
+ s: number;
22
+ l: number;
23
+ }
24
+ export interface OKlch {
25
+ l: number;
26
+ c: number;
27
+ h: number;
28
+ }
29
+ export declare function okhsv_to_oklch(okhsv: OKhsv): OKlch;
30
+ export declare function oklch_to_okhsv(oklch: OKlch): OKhsv;
31
+ export declare function oklab_to_okhsv(oklab: OKlab): OKhsv;
32
+ export declare function okhsv_to_rgb(okhsv: OKhsv): RGB;
33
+ export declare function okhsv_to_oklab(okhsv: OKhsv): OKlab;
34
+ export declare function rgb_to_okhsv(rgb: RGB): OKhsv;
35
+ export declare function rgb_to_oklab(rgb: RGB): OKlab;
36
+ export declare function okhsl_to_rgb(okhsl: OKhsl): RGB;
37
+ export declare function oklab_to_oklch(oklab: OKlab): OKlch;
38
+ export declare function rgb_to_oklch(rgb: RGB): OKlch;
39
+ export declare function oklab_to_rgb(oklab: OKlab): RGB;
40
+ export declare function oklch_to_rgb(oklch: OKlch): RGB;
41
+ export declare function oklch_string_to_oklch(oklch: string): OKlch;
42
+ export declare function rgb_to_hex(rgb: RGB): string;
43
+ export declare function hex_to_rgb(hex: string): RGB;
44
+ export declare function hex_to_okhsv(hex: string): OKhsv;
45
+ export declare function okhsv_to_hex(okhsv: OKhsv): string;
46
+ export declare function convertCSSToHex(css: string): string;
47
+ export { deserialize, convert, listColorSpaces, sRGB, OKLab, OKLCH, OKHSV, OKHSL };
@@ -0,0 +1,96 @@
1
+ import { convert, OKHSL, OKHSV, OKLab, OKLCH, RGBToHex, sRGB, deserialize, listColorSpaces } from '@texel/color';
2
+ export function okhsv_to_oklch(okhsv) {
3
+ const [l, c, h] = convert([okhsv.h, okhsv.s, okhsv.v], OKHSV, OKLCH);
4
+ return { l: l, c: c, h: h };
5
+ }
6
+ export function oklch_to_okhsv(oklch) {
7
+ const [h, s, v] = convert([oklch.h, oklch.c, oklch.l], OKLCH, OKHSV);
8
+ return { h: h ?? 0, s, v };
9
+ }
10
+ export function oklab_to_okhsv(oklab) {
11
+ const [h, s, v] = convert([oklab.l, oklab.a, oklab.b], OKLab, OKHSV);
12
+ return { h: h ?? 0, s, v };
13
+ }
14
+ export function okhsv_to_rgb(okhsv) {
15
+ const [r, g, b] = convert([okhsv.h, okhsv.s, okhsv.v], OKHSV, sRGB);
16
+ return { r, g, b };
17
+ }
18
+ export function okhsv_to_oklab(okhsv) {
19
+ const [l, a, b] = convert([okhsv.h, okhsv.s, okhsv.v], OKHSV, OKLab);
20
+ return { l, a, b };
21
+ }
22
+ export function rgb_to_okhsv(rgb) {
23
+ const [h, s, v] = convert([rgb.r, rgb.g, rgb.b], sRGB, OKHSV);
24
+ return { h, s, v };
25
+ }
26
+ export function rgb_to_oklab(rgb) {
27
+ const [l, a, b] = convert([rgb.r, rgb.g, rgb.b], sRGB, OKLab);
28
+ return { l, a, b };
29
+ }
30
+ export function okhsl_to_rgb(okhsl) {
31
+ const [r, g, b] = convert([okhsl.h, okhsl.s, okhsl.l], OKHSL, sRGB);
32
+ return { r, g, b };
33
+ }
34
+ export function oklab_to_oklch(oklab) {
35
+ const [l, c, h] = convert([oklab.l, oklab.a, oklab.b], OKLab, OKLCH);
36
+ return { l, c, h };
37
+ }
38
+ export function rgb_to_oklch(rgb) {
39
+ const [l, c, h] = convert([rgb.r, rgb.g, rgb.b], sRGB, OKLCH);
40
+ return { l, c, h };
41
+ }
42
+ export function oklab_to_rgb(oklab) {
43
+ const [r, g, b] = convert([oklab.l, oklab.a, oklab.b], OKLab, sRGB);
44
+ return { r, g, b };
45
+ }
46
+ export function oklch_to_rgb(oklch) {
47
+ const [r, g, b] = convert([oklch.l, oklch.c, oklch.h], OKLCH, sRGB);
48
+ return { r, g, b };
49
+ }
50
+ export function oklch_string_to_oklch(oklch) {
51
+ const converted = oklch.split('oklch(')[1].split(')')[0].split(' ');
52
+ return { l: parseFloat(converted[0]), c: parseFloat(converted[1]), h: parseFloat(converted[2]) };
53
+ }
54
+ export function rgb_to_hex(rgb) {
55
+ const r = Math.round(rgb.r * 255);
56
+ const g = Math.round(rgb.g * 255);
57
+ const b = Math.round(rgb.b * 255);
58
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
59
+ }
60
+ export function hex_to_rgb(hex) {
61
+ if (hex.startsWith('#')) {
62
+ hex = hex.slice(1);
63
+ }
64
+ else if (hex.startsWith('0x')) {
65
+ hex = hex.slice(2);
66
+ }
67
+ if (hex.length !== 6) {
68
+ console.warn('Invalid hex color', hex);
69
+ return { r: 0, g: 0, b: 0 };
70
+ }
71
+ const r = parseInt(hex.slice(0, 2), 16);
72
+ const g = parseInt(hex.slice(2, 4), 16);
73
+ const b = parseInt(hex.slice(4, 6), 16);
74
+ const r_ = r / 255;
75
+ const g_ = g / 255;
76
+ const b_ = b / 255;
77
+ return { r: r_, g: g_, b: b_ };
78
+ }
79
+ export function hex_to_okhsv(hex) {
80
+ const rgb = hex_to_rgb(hex);
81
+ return rgb_to_okhsv(rgb);
82
+ }
83
+ export function okhsv_to_hex(okhsv) {
84
+ const rgb = okhsv_to_rgb(okhsv);
85
+ return rgb_to_hex(rgb);
86
+ }
87
+ export function convertCSSToHex(css) {
88
+ const { id, coords } = deserialize(css);
89
+ const space = listColorSpaces().find((f) => id === f.id);
90
+ if (!space) {
91
+ throw new Error('Invalid color space');
92
+ }
93
+ const rgb = convert(coords, space, sRGB);
94
+ return RGBToHex(rgb);
95
+ }
96
+ export { deserialize, convert, listColorSpaces, sRGB, OKLab, OKLCH, OKHSV, OKHSL };
@@ -0,0 +1,5 @@
1
+ export declare const picker_size = 200;
2
+ export declare const slider_width = 16;
3
+ export declare const border_size = 10;
4
+ export declare const gap_size = 30;
5
+ export declare const eps = 0.0001;
@@ -0,0 +1,5 @@
1
+ export const picker_size = 200;
2
+ export const slider_width = 16;
3
+ export const border_size = 10;
4
+ export const gap_size = 30;
5
+ export const eps = 0.0001;
@@ -0,0 +1,3 @@
1
+ export { default } from './ColorPicker.svelte';
2
+ export { default as ColorPicker } from './ColorPicker.svelte';
3
+ export type { RGB, OKlab, OKhsv } from './color';
@@ -0,0 +1,3 @@
1
+ // adapted from https://github.com/CaptainCodeman/svelte-color-select
2
+ export { default } from './ColorPicker.svelte';
3
+ export { default as ColorPicker } from './ColorPicker.svelte';
@@ -0,0 +1,5 @@
1
+ export declare function render_slider_image(canvas: HTMLCanvasElement): void;
2
+ export declare function render_main_image(canvas: HTMLCanvasElement, hue: number): {
3
+ update: (hue: number) => void;
4
+ destroy(): void;
5
+ };
@@ -0,0 +1,77 @@
1
+ import { f_shader, v_shader } from './shaders';
2
+ import { picker_size, slider_width } from './constants';
3
+ import { okhsl_to_rgb } from './color';
4
+ export function render_slider_image(canvas) {
5
+ const ctx = canvas.getContext('2d');
6
+ const data = new Uint8ClampedArray(picker_size * slider_width * 4);
7
+ for (let i = 0; i < picker_size; i++) {
8
+ const a_ = Math.cos((2 * Math.PI * i) / picker_size);
9
+ const b_ = Math.sin((2 * Math.PI * i) / picker_size);
10
+ const rgb = okhsl_to_rgb({
11
+ h: (i / picker_size) * 360,
12
+ s: 0.9,
13
+ l: 0.65 + 0.17 * b_ - 0.08 * a_
14
+ });
15
+ for (let j = 0; j < slider_width; j++) {
16
+ const index = 4 * (i * slider_width + j);
17
+ data[index + 0] = rgb.r * 255;
18
+ data[index + 1] = rgb.g * 255;
19
+ data[index + 2] = rgb.b * 255;
20
+ data[index + 3] = 255;
21
+ }
22
+ }
23
+ const imageData = new ImageData(data, slider_width);
24
+ ctx.putImageData(imageData, 0, 0);
25
+ }
26
+ export function render_main_image(canvas, hue) {
27
+ const gl = canvas.getContext('webgl');
28
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
29
+ const shaderProgram = initShaderProgram(gl, v_shader, f_shader);
30
+ const position = gl.getAttribLocation(shaderProgram, 'position');
31
+ const resolution = gl.getUniformLocation(shaderProgram, 'resolution');
32
+ const hue_rad = gl.getUniformLocation(shaderProgram, 'hue_rad');
33
+ gl.useProgram(shaderProgram);
34
+ gl.uniform2fv(resolution, [gl.canvas.width, gl.canvas.height]);
35
+ const positions = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0];
36
+ const positionBuffer = gl.createBuffer();
37
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
38
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
39
+ gl.enableVertexAttribArray(position);
40
+ gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
41
+ function update(hue) {
42
+ requestAnimationFrame(() => {
43
+ gl.uniform1f(hue_rad, hue / 360);
44
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
45
+ });
46
+ }
47
+ update(hue);
48
+ return {
49
+ update,
50
+ destroy() {
51
+ gl.deleteProgram(shaderProgram);
52
+ }
53
+ };
54
+ }
55
+ function initShaderProgram(gl, vsSource, fsSource) {
56
+ const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
57
+ const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
58
+ const shaderProgram = gl.createProgram();
59
+ gl.attachShader(shaderProgram, vertexShader);
60
+ gl.attachShader(shaderProgram, fragmentShader);
61
+ gl.linkProgram(shaderProgram);
62
+ if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
63
+ throw `unable to init shader: ${gl.getProgramInfoLog(shaderProgram)}`;
64
+ }
65
+ return shaderProgram;
66
+ }
67
+ function loadShader(gl, type, source) {
68
+ const shader = gl.createShader(type);
69
+ gl.shaderSource(shader, source);
70
+ gl.compileShader(shader);
71
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
72
+ const info = gl.getShaderInfoLog(shader);
73
+ gl.deleteShader(shader);
74
+ throw `error compiling shaders: ${info}`;
75
+ }
76
+ return shader;
77
+ }
@@ -0,0 +1,2 @@
1
+ export declare const f_shader: string;
2
+ export declare const v_shader: string;
@@ -0,0 +1,8 @@
1
+ var _ = `precision mediump float;uniform vec2 resolution;uniform float hue_rad;precision mediump float;
2
+ #define M_PI 3.1415926536
3
+ float cbrt(float x){return sign(x)*pow(abs(x),1.0/3.0);}float srgb_transfer_function(float a){return .0031308>=a ? 12.92*a : 1.055*pow(a,.4166666666666667)-.055;}vec3 oklab_to_linear_srgb(vec3 c){float l_=c.x+0.3963377774*c.y+0.2158037573*c.z;float m_=c.x-0.1055613458*c.y-0.0638541728*c.z;float s_=c.x-0.0894841775*c.y-1.2914855480*c.z;float l=l_*l_*l_;float m=m_*m_*m_;float s=s_*s_*s_;return vec3(+4.0767416621*l-3.3077115913*m+0.2309699292*s,-1.2684380046*l+2.6097574011*m-0.3413193965*s,-0.0041960863*l-0.7034186147*m+1.7076147010*s);}float compute_max_saturation(float a,float b){float k0,k1,k2,k3,k4,wl,wm,ws;if(-1.88170328*a-0.80936493*b>1.0){k0=+1.19086277;k1=+1.76576728;k2=+0.59662641;k3=+0.75515197;k4=+0.56771245;wl=+4.0767416621;wm=-3.3077115913;ws=+0.2309699292;}else if(1.81444104*a-1.19445276*b>1.0){k0=+0.73956515;k1=-0.45954404;k2=+0.08285427;k3=+0.12541070;k4=+0.14503204;wl=-1.2684380046;wm=+2.6097574011;ws=-0.3413193965;}else{k0=+1.35733652;k1=-0.00915799;k2=-1.15130210;k3=-0.50559606;k4=+0.00692167;wl=-0.0041960863;wm=-0.7034186147;ws=+1.7076147010;}float S=k0+k1*a+k2*b+k3*a*a+k4*a*b;float k_l=+0.3963377774*a+0.2158037573*b;float k_m=-0.1055613458*a-0.0638541728*b;float k_s=-0.0894841775*a-1.2914855480*b;{float l_=1.0+S*k_l;float m_=1.0+S*k_m;float s_=1.0+S*k_s;float l=l_*l_*l_;float m=m_*m_*m_;float s=s_*s_*s_;float l_dS=3.0*k_l*l_*l_;float m_dS=3.0*k_m*m_*m_;float s_dS=3.0*k_s*s_*s_;float l_dS2=6.0*k_l*k_l*l_;float m_dS2=6.0*k_m*k_m*m_;float s_dS2=6.0*k_s*k_s*s_;float f=wl*l+wm*m+ws*s;float f1=wl*l_dS+wm*m_dS+ws*s_dS;float f2=wl*l_dS2+wm*m_dS2+ws*s_dS2;S=S-f*f1/(f1*f1-0.5*f*f2);}return S;}vec2 find_cusp(float a,float b){float S_cusp=compute_max_saturation(a,b);vec3 rgb_at_max=oklab_to_linear_srgb(vec3(1,S_cusp*a,S_cusp*b));float L_cusp=cbrt(1.0/max(max(rgb_at_max.r,rgb_at_max.g),rgb_at_max.b));float C_cusp=L_cusp*S_cusp;return vec2(L_cusp,C_cusp);}float toe_inv(float x){float k_1=0.206;float k_2=0.03;float k_3=(1.0+k_1)/(1.0+k_2);return(x*x+k_1*x)/(k_3*(x+k_2));}vec2 to_ST(vec2 cusp){float L=cusp.x;float C=cusp.y;return vec2(C/L,C/(1.0-L));}vec3 okhsv_to_srgb(vec3 hsv){float h=hsv.x;float s=hsv.y;float v=hsv.z;float a_=cos(2.0*M_PI*h);float b_=sin(2.0*M_PI*h);vec2 cusp=find_cusp(a_,b_);vec2 ST_max=to_ST(cusp);float S_max=ST_max.x;float T_max=ST_max.y;float S_0=0.5;float k=1.0-S_0/S_max;float L_v=1.0-s*S_0/(S_0+T_max-T_max*k*s);float C_v=s*T_max*S_0/(S_0+T_max-T_max*k*s);float L=v*L_v;float C=v*C_v;float L_vt=toe_inv(L_v);float C_vt=C_v*L_vt/L_v;float L_new=toe_inv(L);C=C*L_new/L;L=L_new;vec3 rgb_scale=oklab_to_linear_srgb(vec3(L_vt,a_*C_vt,b_*C_vt));float scale_L=cbrt(1.0/max(max(rgb_scale.r,rgb_scale.g),max(rgb_scale.b,0.0)));L=L*scale_L;C=C*scale_L;vec3 rgb=oklab_to_linear_srgb(vec3(L,C*a_,C*b_));return vec3(srgb_transfer_function(rgb.r),srgb_transfer_function(rgb.g),srgb_transfer_function(rgb.b));}void main(){vec2 uv=gl_FragCoord.xy/resolution;float l=uv.y;float h=hue_rad;vec3 hsl=vec3(h,uv.x,uv.y);vec3 hsvRGB=okhsv_to_srgb(hsl);gl_FragColor=vec4(hsvRGB,1.0);}`, a = "attribute vec4 position;void main(){gl_Position=position;}";
4
+ const l = _, t = a;
5
+ export {
6
+ l as f_shader,
7
+ t as v_shader
8
+ };
@@ -0,0 +1,2 @@
1
+ export { default as ColorPicker } from './base/ColorPicker.svelte';
2
+ export { default as PopoverColorPicker } from './popover/PopoverColorPicker.svelte';
@@ -0,0 +1,2 @@
1
+ export { default as ColorPicker } from './base/ColorPicker.svelte';
2
+ export { default as PopoverColorPicker } from './popover/PopoverColorPicker.svelte';
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import { cn, Popover } from '@foxui/core';
3
+
4
+ import { ColorPicker, type OKhsv, type OKlab, type RGB } from '../base';
5
+
6
+ import { okhsv_to_rgb, oklab_to_rgb, type OKlch } from '../base/color';
7
+
8
+ let {
9
+ rgb = $bindable(),
10
+ oklab = $bindable(),
11
+ okhsv = $bindable(),
12
+ sideOffset = 10,
13
+ side = 'bottom',
14
+ class: className,
15
+ onchange,
16
+ quickSelects = $bindable([]),
17
+ ...restProps
18
+ }: {
19
+ rgb?: RGB;
20
+ oklab?: OKlab;
21
+ okhsv?: OKhsv;
22
+ class?: string;
23
+ sideOffset?: number;
24
+ side?: 'top' | 'right' | 'bottom' | 'left';
25
+ onchange?: (color: { hex: string; rgb: RGB; oklab: OKlab; okhsv: OKhsv; oklch: OKlch }) => void;
26
+ quickSelects?: {
27
+ label: string;
28
+ rgb?: RGB;
29
+ oklab?: OKlab;
30
+ okhsv?: OKhsv;
31
+ }[];
32
+ } = $props();
33
+
34
+ let internalColor = $derived(convertToInternal(rgb, oklab, okhsv));
35
+
36
+ function convertToInternal(
37
+ rgb: RGB | undefined,
38
+ oklab: OKlab | undefined,
39
+ okhsv: OKhsv | undefined
40
+ ): RGB {
41
+ if (okhsv) {
42
+ return okhsv_to_rgb(okhsv);
43
+ }
44
+
45
+ if (oklab) {
46
+ return oklab_to_rgb(oklab);
47
+ }
48
+
49
+ if (rgb) {
50
+ return rgb;
51
+ }
52
+
53
+ throw 'rgb, oklab, or okhsv required';
54
+ }
55
+ </script>
56
+
57
+ <Popover {side} {sideOffset} class="p-1 pl-2 pr-0">
58
+ {#snippet child({ props })}
59
+ <button
60
+ {...props}
61
+ class={cn(
62
+ 'focus-visible:outline-base-900 dark:focus-visible:outline-base-100 cursor-pointer rounded-full focus-visible:outline-2 focus-visible:outline-offset-2',
63
+ 'group'
64
+ )}
65
+ >
66
+ <div
67
+ class="border-base-300 dark:border-base-700 focus-visible:outline-accent-500 z-10 size-8 rounded-full border group-hover:scale-105 group-active:scale-95 transition-all duration-100"
68
+ style={`background-color: rgb(${internalColor.r * 255}, ${internalColor.g * 255}, ${internalColor.b * 255});`}
69
+ ></div>
70
+ <span class="sr-only">Pick a color</span>
71
+ </button>
72
+ {/snippet}
73
+
74
+ <ColorPicker bind:rgb bind:oklab bind:okhsv class={className} {onchange} {quickSelects} {...restProps} />
75
+ </Popover>
@@ -0,0 +1,26 @@
1
+ import { type OKhsv, type OKlab, type RGB } from '../base';
2
+ import { type OKlch } from '../base/color';
3
+ type $$ComponentProps = {
4
+ rgb?: RGB;
5
+ oklab?: OKlab;
6
+ okhsv?: OKhsv;
7
+ class?: string;
8
+ sideOffset?: number;
9
+ side?: 'top' | 'right' | 'bottom' | 'left';
10
+ onchange?: (color: {
11
+ hex: string;
12
+ rgb: RGB;
13
+ oklab: OKlab;
14
+ okhsv: OKhsv;
15
+ oklch: OKlch;
16
+ }) => void;
17
+ quickSelects?: {
18
+ label: string;
19
+ rgb?: RGB;
20
+ oklab?: OKlab;
21
+ okhsv?: OKhsv;
22
+ }[];
23
+ };
24
+ declare const PopoverColorPicker: import("svelte").Component<$$ComponentProps, {}, "rgb" | "oklab" | "okhsv" | "quickSelects">;
25
+ type PopoverColorPicker = ReturnType<typeof PopoverColorPicker>;
26
+ export default PopoverColorPicker;
@@ -0,0 +1,82 @@
1
+ <script lang="ts">
2
+ import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn } from '@foxui/core';
5
+
6
+ type Color = { class?: string; label: string; value?: string } | string;
7
+
8
+ let {
9
+ ref = $bindable(null),
10
+
11
+ class: className,
12
+ colorsClass,
13
+ colors = $bindable([]),
14
+ selected = $bindable(colors[0]),
15
+
16
+ name = $bindable(crypto.randomUUID()),
17
+
18
+ onselected,
19
+ ...restProps
20
+ }: WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & {
21
+ colors: Color[];
22
+
23
+ selected?: Color;
24
+
25
+ colorsClass?: string;
26
+
27
+ name?: string;
28
+
29
+ onselected?: (color: Color, previous: Color) => void;
30
+ } = $props();
31
+
32
+ function getLabel(c: Color) {
33
+ if (typeof c === 'string') return c;
34
+ return c.label;
35
+ }
36
+
37
+ function getValue(c: Color) {
38
+ if (typeof c === 'string') return c;
39
+ return c.value;
40
+ }
41
+
42
+ function getColorClass(c: Color) {
43
+ if (typeof c === 'string') return '';
44
+ return c.class;
45
+ }
46
+ </script>
47
+
48
+ <div
49
+ class={cn('group flex flex-wrap items-center gap-2', className)}
50
+ {...restProps}
51
+ bind:this={ref}
52
+ >
53
+ {#each colors as color}
54
+ <label
55
+ aria-label={getLabel(color)}
56
+ class={cn(
57
+ 'group relative flex cursor-pointer items-center justify-center rounded-full p-0.5 ring-current',
58
+ 'has-focus-visible:outline-base-900 dark:has-focus-visible:outline-base-100 has-focus-visible:outline-2 has-focus-visible:outline-offset-0',
59
+ 'has-checked:ring-3 hover:scale-105 active:scale-95 transition-all duration-100',
60
+ colorsClass,
61
+ getColorClass(color)
62
+ )}
63
+ style={getValue(color) ? `color: ${getValue(color)}` : undefined}
64
+ >
65
+ <input
66
+ type="radio"
67
+ {name}
68
+ value={getLabel(color)}
69
+ class="sr-only"
70
+ onchange={() => {
71
+ let previous = selected;
72
+ selected = color;
73
+
74
+ if (onselected) onselected(color, previous);
75
+ }}
76
+ checked={getLabel(selected) === getLabel(color)}
77
+ />
78
+
79
+ <span aria-hidden="true" class="size-8 rounded-full bg-current"></span>
80
+ </label>
81
+ {/each}
82
+ </div>
@@ -0,0 +1,17 @@
1
+ import type { WithElementRef, WithoutChildrenOrChild } from 'bits-ui';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type Color = {
4
+ class?: string;
5
+ label: string;
6
+ value?: string;
7
+ } | string;
8
+ type $$ComponentProps = WithElementRef<WithoutChildrenOrChild<HTMLAttributes<HTMLDivElement>>> & {
9
+ colors: Color[];
10
+ selected?: Color;
11
+ colorsClass?: string;
12
+ name?: string;
13
+ onselected?: (color: Color, previous: Color) => void;
14
+ };
15
+ declare const ColorSelect: import("svelte").Component<$$ComponentProps, {}, "colors" | "ref" | "name" | "selected">;
16
+ type ColorSelect = ReturnType<typeof ColorSelect>;
17
+ export default ColorSelect;
@@ -0,0 +1 @@
1
+ export { default as ColorSelect } from './ColorSelect.svelte';
@@ -0,0 +1 @@
1
+ export { default as ColorSelect } from './ColorSelect.svelte';
@@ -0,0 +1,5 @@
1
+ export * from './color-gradient-picker';
2
+ export * from './color-picker';
3
+ export * from './color-select';
4
+ export * from './color-picker/base/color';
5
+ export * from './select-theme';
@@ -0,0 +1,5 @@
1
+ export * from './color-gradient-picker';
2
+ export * from './color-picker';
3
+ export * from './color-select';
4
+ export * from './color-picker/base/color';
5
+ export * from './select-theme';
@@ -0,0 +1,124 @@
1
+ <script lang="ts" module>
2
+ let accentColor = $state({
3
+ class: '',
4
+ label: ''
5
+ });
6
+
7
+ let baseColor = $state({
8
+ class: '',
9
+ label: ''
10
+ });
11
+ </script>
12
+
13
+ <script lang="ts">
14
+ import { Paragraph } from '@foxui/core';
15
+ import { ColorSelect } from '../';
16
+ import { onMount } from 'svelte';
17
+
18
+ let accentColors = [
19
+ { class: 'text-red-500', label: 'red' },
20
+ { class: 'text-orange-500', label: 'orange' },
21
+ { class: 'text-amber-500', label: 'amber' },
22
+ { class: 'text-yellow-500', label: 'yellow' },
23
+ { class: 'text-lime-500', label: 'lime' },
24
+ { class: 'text-green-500', label: 'green' },
25
+ { class: 'text-emerald-500', label: 'emerald' },
26
+ { class: 'text-teal-500', label: 'teal' },
27
+ { class: 'text-cyan-500', label: 'cyan' },
28
+ { class: 'text-sky-500', label: 'sky' },
29
+ { class: 'text-blue-500', label: 'blue' },
30
+ { class: 'text-indigo-500', label: 'indigo' },
31
+ { class: 'text-violet-500', label: 'violet' },
32
+ { class: 'text-purple-500', label: 'purple' },
33
+ { class: 'text-fuchsia-500', label: 'fuchsia' },
34
+ { class: 'text-pink-500', label: 'pink' },
35
+ { class: 'text-rose-500', label: 'rose' }
36
+ ];
37
+
38
+ let baseColors = [
39
+ { class: 'text-gray-500', label: 'gray' },
40
+ { class: 'text-stone-500', label: 'stone' },
41
+ { class: 'text-zinc-500', label: 'zinc' },
42
+ { class: 'text-neutral-500', label: 'neutral' },
43
+ { class: 'text-slate-500', label: 'slate' }
44
+ ];
45
+
46
+ let {
47
+ defaultAccentColor = 'pink',
48
+ defaultBaseColor = 'stone',
49
+ selectAccentColor = true,
50
+ selectBaseColor = true,
51
+ onchanged
52
+ }: {
53
+ defaultAccentColor?: string;
54
+ defaultBaseColor?: string;
55
+ selectAccentColor?: boolean;
56
+ selectBaseColor?: boolean;
57
+ onchanged?: (accentColor: string, baseColor: string) => void;
58
+ } = $props();
59
+
60
+ onMount(() => {
61
+ let savedAccentColor = localStorage.getItem('accentColor');
62
+ if (savedAccentColor) {
63
+ accentColor.label = JSON.parse(savedAccentColor);
64
+ } else {
65
+ accentColor.label = defaultAccentColor;
66
+ }
67
+
68
+ let savedBaseColor = localStorage.getItem('baseColor');
69
+ if (savedBaseColor) {
70
+ baseColor.label = JSON.parse(savedBaseColor);
71
+ } else {
72
+ baseColor.label = defaultBaseColor;
73
+ }
74
+ });
75
+ </script>
76
+
77
+ {#if selectAccentColor}
78
+ <Paragraph class="mb-2">Accent Color</Paragraph>
79
+ <ColorSelect
80
+ bind:selected={accentColor}
81
+ colors={accentColors}
82
+ onselected={(color, previous) => {
83
+ if (typeof previous === 'string' || typeof color === 'string') {
84
+ return;
85
+ }
86
+
87
+ document.documentElement.classList.remove(previous.label.toLowerCase());
88
+ document.documentElement.classList.add(color.label.toLowerCase());
89
+
90
+ localStorage.setItem('accentColor', JSON.stringify(color.label));
91
+
92
+ window.dispatchEvent(
93
+ new CustomEvent('theme-changed', { detail: { accentColor: color.label } })
94
+ );
95
+
96
+ onchanged?.(color.label, previous.label);
97
+ }}
98
+ class="w-64"
99
+ />
100
+ {/if}
101
+
102
+ {#if selectBaseColor}
103
+ <Paragraph class="mb-2 mt-4">Base Color</Paragraph>
104
+ <ColorSelect
105
+ bind:selected={baseColor}
106
+ colors={baseColors}
107
+ onselected={(color, previous) => {
108
+ if (typeof previous === 'string' || typeof color === 'string') {
109
+ return;
110
+ }
111
+
112
+ document.documentElement.classList.remove(previous.label.toLowerCase());
113
+ document.documentElement.classList.add(color.label.toLowerCase());
114
+
115
+ localStorage.setItem('baseColor', JSON.stringify(color.label));
116
+
117
+ window.dispatchEvent(
118
+ new CustomEvent('theme-changed', { detail: { baseColor: color.label } })
119
+ );
120
+
121
+ onchanged?.(color.label, previous.label);
122
+ }}
123
+ />
124
+ {/if}
@@ -0,0 +1,10 @@
1
+ type $$ComponentProps = {
2
+ defaultAccentColor?: string;
3
+ defaultBaseColor?: string;
4
+ selectAccentColor?: boolean;
5
+ selectBaseColor?: boolean;
6
+ onchanged?: (accentColor: string, baseColor: string) => void;
7
+ };
8
+ declare const SelectTheme: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type SelectTheme = ReturnType<typeof SelectTheme>;
10
+ export default SelectTheme;