diff --git a/src/App.tsx b/src/App.tsx
index 2eb9908..db7cfe8 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -8,6 +8,7 @@ import { NewScheme } from './pages/NewScheme';
import { SchemeDetail } from './pages/SchemeDetail';
import { SchemeNotFound } from './pages/SchemeNotFound';
import { Schemes } from './pages/Schemes';
+import { TintsShades } from './pages/TintsShades';
import { Tones } from './pages/Tones';
import { Wheels } from './pages/Wheels';
@@ -29,6 +30,7 @@ const routes = createBrowserRouter([
{ path: 'harmonies', element: },
{ path: 'wheels', element: },
{ path: 'tones', element: },
+ { path: 'tints-shades', element: },
],
},
]);
diff --git a/src/page-components/TintsShades/shades.tsx b/src/page-components/TintsShades/shades.tsx
new file mode 100644
index 0000000..bbc999a
--- /dev/null
+++ b/src/page-components/TintsShades/shades.tsx
@@ -0,0 +1,59 @@
+import { last } from 'lodash-es';
+import { useMemo } from 'react';
+import { useColorFunction } from '../../ColorFunctionContext';
+import { FlexColorStand } from '../../components/FlexColorStand';
+
+type ShadesListProps = {
+ color: string;
+ shades: number;
+ mix: 'progressive' | 'linear' | 'average';
+ step?: number;
+ maximum?: number;
+ copyMode: 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
+};
+
+export function Shades({ color, shades, mix, step, maximum, copyMode }: ShadesListProps) {
+ const { colorFn } = useColorFunction();
+ const colors = useMemo(() => {
+ try {
+ if (!colorFn) {
+ return Array.from({ length: shades + 1 }, () => color);
+ }
+ const genColors = [color];
+ switch (mix) {
+ case 'progressive':
+ for (let i = 1; i <= shades; i++) {
+ const shade = colorFn!.shade(last(genColors), step);
+ genColors.push(shade);
+ }
+ break;
+ case 'linear':
+ for (let i = 1; i <= shades; i++) {
+ const shade = colorFn!.shade(color, step * i);
+ genColors.push(shade);
+ }
+ break;
+ case 'average': {
+ const interval = maximum / shades / 100;
+ for (let i = 1; i <= shades; i++) {
+ const shade = colorFn!.shade(color, interval * i);
+ genColors.push(shade);
+ }
+ break;
+ }
+ }
+ return genColors.reverse();
+ } catch (e) {
+ console.error('[Generate Shades]', e);
+ }
+ return Array.from({ length: shades + 1 }, () => color);
+ }, [color, shades, mix, step, maximum]);
+
+ return (
+ <>
+ {colors.map((c, index) => (
+
+ ))}
+ >
+ );
+}
diff --git a/src/page-components/TintsShades/tints.tsx b/src/page-components/TintsShades/tints.tsx
new file mode 100644
index 0000000..748ec1c
--- /dev/null
+++ b/src/page-components/TintsShades/tints.tsx
@@ -0,0 +1,59 @@
+import { last } from 'lodash-es';
+import { useMemo } from 'react';
+import { useColorFunction } from '../../ColorFunctionContext';
+import { FlexColorStand } from '../../components/FlexColorStand';
+
+type TintsListProps = {
+ color: string;
+ tints: number;
+ mix: 'progressive' | 'linear' | 'average';
+ step?: number;
+ maximum?: number;
+ copyMode: 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
+};
+
+export function Tints({ color, tints, mix, step, maximum, copyMode }: TintsListProps) {
+ const { colorFn } = useColorFunction();
+ const colors = useMemo(() => {
+ try {
+ if (!colorFn) {
+ return Array.from({ length: tints + 1 }, () => color);
+ }
+ const genColors = [color];
+ switch (mix) {
+ case 'progressive':
+ for (let i = 1; i <= tints; i++) {
+ const tint = colorFn!.tint(last(genColors), step);
+ genColors.push(tint);
+ }
+ break;
+ case 'linear':
+ for (let i = 1; i <= tints; i++) {
+ const tint = colorFn!.tint(color, step * i);
+ genColors.push(tint);
+ }
+ break;
+ case 'average': {
+ const interval = maximum / tints / 100;
+ for (let i = 1; i <= tints; i++) {
+ const tint = colorFn!.tint(color, interval * i);
+ genColors.push(tint);
+ }
+ break;
+ }
+ }
+ return genColors;
+ } catch (e) {
+ console.error('[Generate Tints]', e);
+ }
+ return Array.from({ length: tints + 1 }, () => color);
+ }, [color, tints, mix, step, maximum]);
+
+ return (
+ <>
+ {colors.map((c, index) => (
+
+ ))}
+ >
+ );
+}
diff --git a/src/pages/TintsShades.module.css b/src/pages/TintsShades.module.css
new file mode 100644
index 0000000..782a354
--- /dev/null
+++ b/src/pages/TintsShades.module.css
@@ -0,0 +1,55 @@
+@layer pages {
+ .tints_workspace {
+ flex-direction: column;
+ }
+ .explore_section {
+ width: 100%;
+ flex: 1;
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ gap: var(--spacing-m);
+ }
+ .function_side {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-m);
+ font-size: var(--font-size-s);
+ .mode_navigation {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: var(--spacing-s);
+ }
+ h5 {
+ padding-block: var(--spacing-m);
+ font-size: var(--font-size-m);
+ }
+ }
+ .tints_content {
+ flex: 1;
+ padding: 0 var(--spacing-m);
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-s);
+ h5 {
+ padding-block: var(--spacing-m);
+ font-size: var(--font-size-m);
+ }
+ .color_value_mode {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: var(--spacing-s);
+ font-size: var(--font-size-s);
+ }
+ .colors_booth {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: stretch;
+ gap: var(--spacing-xs);
+ min-height: 12em;
+ }
+ }
+}
diff --git a/src/pages/TintsShades.tsx b/src/pages/TintsShades.tsx
new file mode 100644
index 0000000..8f4376d
--- /dev/null
+++ b/src/pages/TintsShades.tsx
@@ -0,0 +1,134 @@
+import cx from 'clsx';
+import { useAtom } from 'jotai';
+import { isEqual } from 'lodash-es';
+import { useState } from 'react';
+import { ColorPicker } from '../components/ColorPicker';
+import { HSegmentedControl } from '../components/HSegmentedControl';
+import { Labeled } from '../components/Labeled';
+import { LabeledPicker } from '../components/LabeledPicker';
+import { ScrollArea } from '../components/ScrollArea';
+import { VSegmentedControl } from '../components/VSegmentedControl';
+import { Shades } from '../page-components/TintsShades/shades';
+import { Tints } from '../page-components/TintsShades/tints';
+import { currentPickedColor } from '../stores/colors';
+import styles from './TintsShades.module.css';
+
+export function TintsShades() {
+ const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
+ const [steps, setSteps] = useState(10);
+ const [tints, setTints] = useState(3);
+ const [shades, setShades] = useState(3);
+ const [maximum, setMaximum] = useState(90);
+ const [mixMode, setMixMode] = useState<'progressive' | 'average'>('progressive');
+ const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
+
+ return (
+
+
+
+
+
+
+
Tints
+
+
+
+
Shades
+
+
+
+
+
+
+
+
+
+
+
+ );
+}