-
Notifications
You must be signed in to change notification settings - Fork 81
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
Add field type fields.color()
#1279
Comments
@zanhk I can create and add custom field components? That sounds great! Do you know of an example on this or some docs? |
@tordans yes you can create custom fields components, I don't think there is docs about it. for usage you can simply use like this
|
If people need I made an icon picker as well import { Icon } from "@iconify/react";
import { FieldPrimitive } from "@keystar/ui/field";
import type { BasicFormField } from "@keystatic/core";
import { useEffect, useState } from "react";
import AsyncSelect from "react-select/async";
import packageJson from "../package.json";
const customStyles = {
control: (provided: any, state: { isFocused: boolean }) => ({
...provided,
fontFamily: "var(--kui-typography-font-family-base)",
paddingBlock: "var(--kui-size-space-small)",
paddingInline: "var(--kui-size-space-medium)",
paddingLeft: "6px",
paddingRight: "0",
maxWidth: "20rem",
minHeight: "32px",
height: "32px",
}),
valueContainer: (provided: any) => ({
...provided,
height: "30px",
paddingLeft: "2px",
paddingTop: "0",
paddingBottom: "8px",
}),
input: (provided: any) => ({
...provided,
fontFamily: "var(--kui-typography-font-family-base)",
fontWeight: "var(--kui-typography-font-weight-regular)",
fontSize: "var(--kui-typography-text-regular-size)",
margin: "0px",
}),
singleValue: (provided: any) => ({
...provided,
fontFamily: "var(--kui-typography-font-family-base)",
fontWeight: "var(--kui-typography-font-weight-regular)",
fontSize: "var(--kui-typography-text-regular-size)",
display: "flex",
alignItems: "center",
}),
menu: (provided: any) => ({
...provided,
fontFamily: "var(--kui-typography-font-family-base)",
maxWidth: "20rem",
}),
option: (provided: any, state: { isFocused: boolean }) => ({
...provided,
fontFamily: "var(--kui-typography-font-family-base)",
display: "flex",
alignItems: "center",
}),
indicatorSeparator: () => ({
display: "none",
}),
indicatorsContainer: (provided: any) => ({
...provided,
height: "30px",
paddingBottom: "6px",
paddingRight: "0",
}),
container: (provided: any) => ({
...provided,
maxWidth: "20rem",
}),
};
function getIconifySets() {
const iconifySets = Object.entries(packageJson.devDependencies || {})
.filter(([key]) => key.startsWith("@iconify-json/"))
.map(([key, version]) => {
const setName = key.split("/")[1];
return {
key: setName,
prefix: `${setName}:`,
url: `https://cdn.jsdelivr.net/npm/${key}@${version}/icons.json`,
};
});
return iconifySets;
}
const iconSets = getIconifySets();
async function fetchIconOptions(inputValue: string) {
const options = [];
for (const set of iconSets) {
try {
const response = await fetch(set.url);
const data = await response.json();
const icons = Object.keys(data.icons)
.filter((icon) => icon.toLowerCase().includes(inputValue.toLowerCase()))
.slice(0, 50);
options.push(
...icons.map((icon) => ({
label: `${set.key}:${icon}`,
value: `${set.prefix}${icon}`,
icon: `${set.prefix}${icon}`,
})),
);
} catch (error) {
console.error(`Error fetching icons for ${set.key}:`, error);
}
}
return options;
}
export function IconPicker({
label,
defaultValue = "mdi:home",
description,
}: {
label: string;
defaultValue?: string;
description?: string;
}): BasicFormField<string> {
return {
kind: "form",
Input({ value, onChange, autoFocus, forceValidation }) {
const [selectedOption, setSelectedOption] = useState<{ value: string; label: string; icon: string } | null>(null);
useEffect(() => {
if (value) {
const [prefix, iconName] = value.split(":");
setSelectedOption({ value, label: `${prefix}:${iconName}`, icon: value });
}
}, [value]);
const handleChange = (option: { value: string; label: string; icon: string } | null) => {
setSelectedOption(option);
onChange(option?.value ?? "");
};
const loadOptions = (inputValue: string) =>
new Promise<{ label: string; value: string; icon: string }[]>((resolve) => {
setTimeout(() => {
fetchIconOptions(inputValue).then(resolve);
}, 300);
});
const customOption = ({ data, innerProps, isFocused, isSelected }) => (
<div
{...innerProps}
style={{
display: "flex",
alignItems: "center",
padding: "8px",
color: isSelected ? "white" : "var(--kui-color-foreground-neutral)",
background: isSelected ? "#3a5ccc" : isFocused ? "#f9f1fe" : "transparent",
}}
>
<Icon icon={data.icon} style={{ marginRight: "8px" }} />
{data.label}
</div>
);
return (
<FieldPrimitive label={label} description={description}>
<>
<AsyncSelect
cacheOptions
defaultOptions
loadOptions={loadOptions}
value={selectedOption}
onChange={handleChange}
placeholder="Search for an icon..."
autoFocus={autoFocus}
styles={customStyles}
components={{ Option: customOption }}
menuPortalTarget={document.body}
formatOptionLabel={(option) => (
<div style={{ display: "flex", alignItems: "center" }}>
<Icon icon={option.icon} style={{ marginRight: "8px" }} />
<span>{option.label}</span>
</div>
)}
/>
<small
style={{
fontFamily: "var(--kui-typography-font-family-base)",
color: "var(--kui-color-content-subtle)",
paddingTop: 0,
paddingLeft: "2px",
paddingBottom: 0,
marginTop: "-2px",
marginBottom: 0,
}}
>
Find all available icons at{" "}
<a href="https://icon-sets.iconify.design/" target="_blank" rel="noopener noreferrer">
Iconify Icon Sets
</a>{" "}
(available sets: {iconSets.map((set) => set.key).join(", ")})
</small>
</>
</FieldPrimitive>
);
},
defaultValue() {
return defaultValue;
},
parse(value) {
return value as string;
},
serialize(value) {
return { value };
},
validate(value) {
return value;
},
reader: {
parse(value) {
return value as string;
},
},
};
} |
Nice @zanhk |
@ismaelrumzan Show me how you are using it and what error you are getting |
Thanks, here is part of my config (it's part of a singleton)
And the front-end error when loading the admin page
|
Hi @ismaelrumzan sorry for the delayed response, it could be the configuration on tsconfig.json maybe? I will make some repo using ColorPicker open source so you can take a look there |
It would be great to have a
fields.color
field that shows the native color picker of the browser and returns a hex value.The text was updated successfully, but these errors were encountered: