From 409161acca740af46af3093f13e9b1544c099115 Mon Sep 17 00:00:00 2001 From: Silvano Stralla Date: Wed, 24 Dec 2025 12:23:49 +0100 Subject: [PATCH 1/2] Add support for using an emoji as a plugin icon --- .claude/settings.local.json | 7 ++++++ packages/sdk/package-lock.json | 15 ++++++++++++ packages/sdk/package.json | 3 ++- packages/sdk/src/guardUtils.ts | 13 ++++++++++ packages/sdk/src/icon.ts | 44 +++++++++++++++++++++++++++------- 5 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..7867010 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(npm ls:*)" + ] + } +} diff --git a/packages/sdk/package-lock.json b/packages/sdk/package-lock.json index eccac2f..e92081d 100644 --- a/packages/sdk/package-lock.json +++ b/packages/sdk/package-lock.json @@ -12,6 +12,7 @@ "@datocms/cma-client": "*", "@types/react": "^17.0.3", "datocms-structured-text-utils": "^2.0.0", + "emoji-regex-xs": "*", "penpal": "^4.1.1" }, "devDependencies": { @@ -200,6 +201,15 @@ "dev": true, "license": "MIT" }, + "node_modules/emoji-regex-xs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-2.0.1.tgz", + "integrity": "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -782,6 +792,11 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "emoji-regex-xs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-2.0.1.tgz", + "integrity": "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==" + }, "foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 95759b2..67e2b72 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -39,7 +39,8 @@ "@datocms/cma-client": "*", "@types/react": "^17.0.3", "datocms-structured-text-utils": "^2.0.0", - "penpal": "^4.1.1" + "penpal": "^4.1.1", + "emoji-regex-xs": "*" }, "devDependencies": { "glob": "^11.0.0", diff --git a/packages/sdk/src/guardUtils.ts b/packages/sdk/src/guardUtils.ts index eefc1ca..43cc6a8 100644 --- a/packages/sdk/src/guardUtils.ts +++ b/packages/sdk/src/guardUtils.ts @@ -1,3 +1,7 @@ +import emojiRegex from 'emoji-regex-xs'; + +export type Emoji = string & { readonly __brand: 'Emoji' }; + export function isNullish(value: unknown): value is null | undefined { return value === null || value === undefined; } @@ -10,6 +14,15 @@ export function isString(value: unknown): value is string { return typeof value === 'string'; } +export function isEmoji(value: unknown): value is Emoji { + if (!isString(value)) return false; + + const regex = emojiRegex(); + const match = value.match(regex); + + return match !== null && match.length === 1 && match[0] === value; +} + export function isNumber(value: unknown): value is number { return typeof value === 'number'; } diff --git a/packages/sdk/src/icon.ts b/packages/sdk/src/icon.ts index b7eac44..e21787d 100644 --- a/packages/sdk/src/icon.ts +++ b/packages/sdk/src/icon.ts @@ -1,6 +1,10 @@ -import { isRecord, isString } from './guardUtils.js'; +import { isEmoji, isRecord, isString } from './guardUtils.js'; -export type Icon = AwesomeFontIconIdentifier | SvgDefinition; +export type Icon = AwesomeFontIconIdentifier | SvgDefinition | EmojiDefinition; + +export function isIcon(value: unknown): value is Icon { + return isString(value) || isSvgDefinition(value) || isEmojiDefinition(value); +} /** * Defines a custom SVG icon for use in DatoCMS plugins. @@ -40,16 +44,40 @@ export type SvgDefinition = { content: string; }; -export function isIcon(value: unknown): value is Icon { +export function isSvgDefinition(value: unknown): value is SvgDefinition { return ( - isString(value) || - (isRecord(value) && - value.type === 'svg' && - isString(value.viewBox) && - isString(value.content)) + isRecord(value) && + value.type === 'svg' && + isString(value.viewBox) && + isString(value.content) ); } +/** + * Defines an emoji icon for use in DatoCMS plugins. + * + * @example + * ```typescript + * const starIcon: EmojiDefinition = { + * type: 'emoji', + * emoji: '⭐' + * }; + * ``` + */ +export type EmojiDefinition = { + /** Always set to 'emoji' to indicate this is an emoji icon */ + type: 'emoji'; + + /** + * A string contaning a single emoji. + */ + emoji: string; +}; + +export function isEmojiDefinition(value: unknown): value is EmojiDefinition { + return isRecord(value) && value.type === 'emoji' && isEmoji(value.emoji); +} + /** * Font Awesome icon identifier for use in DatoCMS plugins. * From 8efb820dfce1eb3b88b8894943ae242c278b9811 Mon Sep 17 00:00:00 2001 From: Silvano Stralla Date: Wed, 24 Dec 2025 12:27:24 +0100 Subject: [PATCH 2/2] v2.1.0-alpha.0 --- lerna.json | 2 +- packages/react-ui/package-lock.json | 6 +++--- packages/react-ui/package.json | 4 ++-- packages/sdk/package-lock.json | 4 ++-- packages/sdk/package.json | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lerna.json b/lerna.json index b0d0fc5..791bfbf 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": [ "packages/*" ], - "version": "2.0.19" + "version": "2.1.0-alpha.0" } diff --git a/packages/react-ui/package-lock.json b/packages/react-ui/package-lock.json index 0893918..07fdc28 100644 --- a/packages/react-ui/package-lock.json +++ b/packages/react-ui/package-lock.json @@ -1,17 +1,17 @@ { "name": "datocms-react-ui", - "version": "2.0.19", + "version": "2.1.0-alpha.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "datocms-react-ui", - "version": "2.0.19", + "version": "2.1.0-alpha.0", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.27.16", "classnames": "^2.3.1", - "datocms-plugin-sdk": "^2.0.18", + "datocms-plugin-sdk": "^2.1.0-alpha.0", "react-intersection-observer": "^8.31.0", "react-select": "^5.2.1", "scroll-into-view-if-needed": "^2.2.20" diff --git a/packages/react-ui/package.json b/packages/react-ui/package.json index 4d2d24f..936cf91 100644 --- a/packages/react-ui/package.json +++ b/packages/react-ui/package.json @@ -1,6 +1,6 @@ { "name": "datocms-react-ui", - "version": "2.0.19", + "version": "2.1.0-alpha.0", "description": "React components to use inside DatoCMS plugins", "keywords": [ "datocms", @@ -42,7 +42,7 @@ "dependencies": { "@floating-ui/react": "^0.27.16", "classnames": "^2.3.1", - "datocms-plugin-sdk": "^2.0.18", + "datocms-plugin-sdk": "^2.1.0-alpha.0", "react-intersection-observer": "^8.31.0", "react-select": "^5.2.1", "scroll-into-view-if-needed": "^2.2.20" diff --git a/packages/sdk/package-lock.json b/packages/sdk/package-lock.json index e92081d..a042fd1 100644 --- a/packages/sdk/package-lock.json +++ b/packages/sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "datocms-plugin-sdk", - "version": "2.0.18", + "version": "2.1.0-alpha.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "datocms-plugin-sdk", - "version": "2.0.18", + "version": "2.1.0-alpha.0", "license": "MIT", "dependencies": { "@datocms/cma-client": "*", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 67e2b72..6b76b45 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "datocms-plugin-sdk", - "version": "2.0.18", + "version": "2.1.0-alpha.0", "description": "DatoCMS Plugin SDK", "keywords": [ "datocms", @@ -39,8 +39,8 @@ "@datocms/cma-client": "*", "@types/react": "^17.0.3", "datocms-structured-text-utils": "^2.0.0", - "penpal": "^4.1.1", - "emoji-regex-xs": "*" + "emoji-regex-xs": "*", + "penpal": "^4.1.1" }, "devDependencies": { "glob": "^11.0.0",