@liiift-studio/sanity-font-manager 2.3.1 → 2.3.3
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.
- package/README.md +52 -0
- package/dist/index.js +156 -0
- package/dist/index.mjs +241 -88
- package/package.json +1 -1
- package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -0
- package/src/components/SetOTF.jsx +87 -0
- package/src/components/StyleCountInput.jsx +16 -0
- package/src/index.js +3 -0
package/README.md
CHANGED
|
@@ -75,6 +75,25 @@ One-click generator for Full Family, Uprights, Italics, and Subfamily collection
|
|
|
75
75
|
import { GenerateCollectionsPairsComponent } from '@liiift-studio/sanity-font-manager';
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
### `PrimaryCollectionGeneratorTypeface`
|
|
79
|
+
|
|
80
|
+
One-click generator for a single full-family collection that includes all fonts linked to the typeface. Prepends the new collection to the existing `styles.collections` array — non-destructive. Uses `SANITY_STUDIO_DEFAULT_COLLECTION_PRICE` as the default price, falling back to `100`.
|
|
81
|
+
|
|
82
|
+
Wire it up on a `string` field in the typeface schema:
|
|
83
|
+
|
|
84
|
+
```jsx
|
|
85
|
+
import { PrimaryCollectionGeneratorTypeface } from '@liiift-studio/sanity-font-manager';
|
|
86
|
+
|
|
87
|
+
{
|
|
88
|
+
name: 'generateCollectionGroup',
|
|
89
|
+
type: 'string',
|
|
90
|
+
title: 'Generate Full Family Collection',
|
|
91
|
+
description: 'Generate a collection that includes all the styles from this typeface.',
|
|
92
|
+
components: { input: PrimaryCollectionGeneratorTypeface },
|
|
93
|
+
hidden: ({ parent }) => !parent?.styles?.fonts?.length,
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
78
97
|
### `FontScriptUploaderComponent`
|
|
79
98
|
|
|
80
99
|
Script-aware uploader for per-script font file variants (Latin, Arabic, Hebrew, etc.) stored in `scriptFileInput` on the font document.
|
|
@@ -91,6 +110,39 @@ Updates and re-links existing script font variant references on font documents
|
|
|
91
110
|
|
|
92
111
|
Recalculates and patches the `subfamily` field on all fonts linked to a typeface, based on the typeface's defined subfamily groups — without re-uploading any files.
|
|
93
112
|
|
|
113
|
+
### `SetOTF`
|
|
114
|
+
|
|
115
|
+
Detects which configured OpenType feature keys are supported by the typeface's first linked font. Reads `opentypeFeatures.chars` from the font document (populated by `generateFontData`) and patches the `features` array on the field. Shows a feature count when features are detected, and clear error messages when font data is missing.
|
|
116
|
+
|
|
117
|
+
Wire it up on the `openType` object field in the typeface schema:
|
|
118
|
+
|
|
119
|
+
```jsx
|
|
120
|
+
import { SetOTF } from '@liiift-studio/sanity-font-manager';
|
|
121
|
+
|
|
122
|
+
{
|
|
123
|
+
name: 'openType',
|
|
124
|
+
type: 'object',
|
|
125
|
+
components: { input: SetOTF },
|
|
126
|
+
options: { collapsible: true },
|
|
127
|
+
fields: [ /* feature fields — each with a `feature` string e.g. 'liga', 'smcp' */ ],
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `StyleCountInput`
|
|
132
|
+
|
|
133
|
+
Displays the total number of font styles (static + variable) linked to a typeface. Reads `styles.fonts` and `styles.variableFont` arrays from the form context. Useful as a read-only display field in the typeface schema.
|
|
134
|
+
|
|
135
|
+
```jsx
|
|
136
|
+
import { StyleCountInput } from '@liiift-studio/sanity-font-manager';
|
|
137
|
+
|
|
138
|
+
{
|
|
139
|
+
name: 'styleCount',
|
|
140
|
+
type: 'number',
|
|
141
|
+
readOnly: true,
|
|
142
|
+
components: { input: StyleCountInput },
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
94
146
|
### `KeyValueInput`
|
|
95
147
|
|
|
96
148
|
Generic ordered key-value editor where both keys and values are plain strings. Supports add, remove, and reorder (up/down arrows). Values are stored as an array of `{ key, value }` objects.
|
package/dist/index.js
CHANGED
|
@@ -36,11 +36,14 @@ __export(index_exports, {
|
|
|
36
36
|
KeyValueInput: () => KeyValueInput,
|
|
37
37
|
KeyValueReferenceInput: () => KeyValueReferenceInput,
|
|
38
38
|
PriceInput: () => PriceInput_default,
|
|
39
|
+
PrimaryCollectionGeneratorTypeface: () => PrimaryCollectionGeneratorTypeface,
|
|
39
40
|
RegenerateSubfamiliesComponent: () => RegenerateSubfamiliesComponent,
|
|
40
41
|
SCRIPTS: () => SCRIPTS,
|
|
41
42
|
SCRIPTS_OBJECT: () => SCRIPTS_OBJECT,
|
|
43
|
+
SetOTF: () => SetOTF,
|
|
42
44
|
SingleUploaderTool: () => SingleUploaderTool,
|
|
43
45
|
StatusDisplay: () => StatusDisplay_default,
|
|
46
|
+
StyleCountInput: () => StyleCountInput,
|
|
44
47
|
UpdateScriptsComponent: () => UpdateScriptsComponent,
|
|
45
48
|
UploadButton: () => UploadButton_default,
|
|
46
49
|
UploadScriptsComponent: () => UploadScriptsComponent,
|
|
@@ -3964,6 +3967,156 @@ function VariableInstanceReferencesInput(props) {
|
|
|
3964
3967
|
));
|
|
3965
3968
|
}
|
|
3966
3969
|
|
|
3970
|
+
// src/components/PrimaryCollectionGeneratorTypeface.jsx
|
|
3971
|
+
var import_react16 = __toESM(require("react"));
|
|
3972
|
+
var import_ui14 = require("@sanity/ui");
|
|
3973
|
+
var import_sanity12 = require("sanity");
|
|
3974
|
+
var import_nanoid10 = require("nanoid");
|
|
3975
|
+
var PrimaryCollectionGeneratorTypeface = () => {
|
|
3976
|
+
const client = useSanityClient();
|
|
3977
|
+
const [status, setStatus] = (0, import_react16.useState)("ready");
|
|
3978
|
+
const [ready, setReady] = (0, import_react16.useState)(true);
|
|
3979
|
+
const [price, setPrice] = (0, import_react16.useState)(
|
|
3980
|
+
process.env.SANITY_STUDIO_DEFAULT_COLLECTION_PRICE || 100
|
|
3981
|
+
);
|
|
3982
|
+
const fonts = (0, import_sanity12.useFormValue)(["styles", "fonts"]);
|
|
3983
|
+
const title = (0, import_sanity12.useFormValue)(["title"]);
|
|
3984
|
+
const preferredStyle = (0, import_sanity12.useFormValue)(["preferredStyle"]);
|
|
3985
|
+
const docId = (0, import_sanity12.useFormValue)(["_id"]);
|
|
3986
|
+
const styles = (0, import_sanity12.useFormValue)(["styles"]);
|
|
3987
|
+
const generateCollection = (0, import_react16.useCallback)(async () => {
|
|
3988
|
+
setStatus("Generating collection...");
|
|
3989
|
+
setReady(false);
|
|
3990
|
+
let id = title.toLowerCase().replace(/\s+/g, "-").slice(0, 200);
|
|
3991
|
+
if (!id.includes("collection")) id += "-collection";
|
|
3992
|
+
const colTitle = id.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
|
|
3993
|
+
const collectionDoc = {
|
|
3994
|
+
_key: (0, import_nanoid10.nanoid)(),
|
|
3995
|
+
_id: id,
|
|
3996
|
+
title: colTitle,
|
|
3997
|
+
slug: { _type: "slug", current: id },
|
|
3998
|
+
price: Number(price) || 0,
|
|
3999
|
+
fonts: Object.values(fonts),
|
|
4000
|
+
preferredStyle,
|
|
4001
|
+
_type: "collection",
|
|
4002
|
+
type: "collection"
|
|
4003
|
+
};
|
|
4004
|
+
try {
|
|
4005
|
+
const sanityCollection = await client.createOrReplace(collectionDoc);
|
|
4006
|
+
const collections = styles.collections || [];
|
|
4007
|
+
await client.patch(docId).setIfMissing({ styles: {} }).set({
|
|
4008
|
+
styles: {
|
|
4009
|
+
...styles,
|
|
4010
|
+
collections: [{
|
|
4011
|
+
_type: "reference",
|
|
4012
|
+
_key: (0, import_nanoid10.nanoid)(),
|
|
4013
|
+
_ref: sanityCollection._id,
|
|
4014
|
+
_weak: true
|
|
4015
|
+
}, ...collections]
|
|
4016
|
+
}
|
|
4017
|
+
}).commit();
|
|
4018
|
+
setStatus("Collection generated");
|
|
4019
|
+
} catch (err) {
|
|
4020
|
+
console.error("Error creating collection:", err.message);
|
|
4021
|
+
setStatus("Error generating collection");
|
|
4022
|
+
}
|
|
4023
|
+
setReady(true);
|
|
4024
|
+
}, [docId, fonts, price, preferredStyle, styles, title, client]);
|
|
4025
|
+
if (!title || !fonts) return null;
|
|
4026
|
+
return /* @__PURE__ */ import_react16.default.createElement(import_ui14.Stack, { space: 2 }, /* @__PURE__ */ import_react16.default.createElement(StatusDisplay_default, { status, error: false }), /* @__PURE__ */ import_react16.default.createElement(import_ui14.Card, { border: true, padding: 2, shadow: 1, radius: 2 }, ready ? /* @__PURE__ */ import_react16.default.createElement(import_ui14.Stack, { space: 3 }, /* @__PURE__ */ import_react16.default.createElement(import_ui14.Flex, { align: "center", gap: 2, marginTop: 1, marginBottom: 1 }, /* @__PURE__ */ import_react16.default.createElement(import_ui14.Text, { size: 1, muted: true }, "Price"), /* @__PURE__ */ import_react16.default.createElement(import_ui14.Text, { size: 1, muted: true }, "$"), /* @__PURE__ */ import_react16.default.createElement(
|
|
4027
|
+
"input",
|
|
4028
|
+
{
|
|
4029
|
+
value: price,
|
|
4030
|
+
onChange: (e) => setPrice(e.target.value),
|
|
4031
|
+
type: "number",
|
|
4032
|
+
style: { textAlign: "end", padding: "5px", maxWidth: "75px" }
|
|
4033
|
+
}
|
|
4034
|
+
), /* @__PURE__ */ import_react16.default.createElement(import_ui14.Text, { size: 1, muted: true }, "per full family")), /* @__PURE__ */ import_react16.default.createElement(
|
|
4035
|
+
import_ui14.Button,
|
|
4036
|
+
{
|
|
4037
|
+
mode: "ghost",
|
|
4038
|
+
tone: "primary",
|
|
4039
|
+
style: { width: "100%" },
|
|
4040
|
+
onClick: generateCollection,
|
|
4041
|
+
text: "Generate Full Family Collection"
|
|
4042
|
+
}
|
|
4043
|
+
)) : /* @__PURE__ */ import_react16.default.createElement(import_ui14.Flex, { align: "center", justify: "center", gap: 3, padding: 4 }, /* @__PURE__ */ import_react16.default.createElement(import_ui14.Spinner, null), /* @__PURE__ */ import_react16.default.createElement(import_ui14.Text, { muted: true, size: 1 }, status))));
|
|
4044
|
+
};
|
|
4045
|
+
|
|
4046
|
+
// src/components/SetOTF.jsx
|
|
4047
|
+
var import_react17 = require("react");
|
|
4048
|
+
var import_sanity13 = require("sanity");
|
|
4049
|
+
var import_ui15 = require("@sanity/ui");
|
|
4050
|
+
var SetOTF = (props) => {
|
|
4051
|
+
var _a, _b;
|
|
4052
|
+
const { onChange, value = {} } = props;
|
|
4053
|
+
const client = useSanityClient();
|
|
4054
|
+
const stylesObject = (0, import_sanity13.useFormValue)(["styles"]);
|
|
4055
|
+
const [message, setMessage] = (0, import_react17.useState)("");
|
|
4056
|
+
const detect = async () => {
|
|
4057
|
+
var _a2, _b2, _c;
|
|
4058
|
+
if (!((_a2 = stylesObject == null ? void 0 : stylesObject.fonts) == null ? void 0 : _a2.length)) {
|
|
4059
|
+
setMessage("Error: No fonts found in styles. Please add at least one font first.");
|
|
4060
|
+
setTimeout(() => setMessage(""), 5e3);
|
|
4061
|
+
return;
|
|
4062
|
+
}
|
|
4063
|
+
const fontRef = (_b2 = stylesObject.fonts[0]) == null ? void 0 : _b2._ref;
|
|
4064
|
+
if (!fontRef) {
|
|
4065
|
+
setMessage("Error: Invalid font reference in styles.");
|
|
4066
|
+
setTimeout(() => setMessage(""), 5e3);
|
|
4067
|
+
return;
|
|
4068
|
+
}
|
|
4069
|
+
try {
|
|
4070
|
+
const font = await client.fetch('*[_type == "font" && _id == $id][0]', { id: fontRef });
|
|
4071
|
+
if (!font) {
|
|
4072
|
+
setMessage("Error: Could not find the referenced font.");
|
|
4073
|
+
setTimeout(() => setMessage(""), 5e3);
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
if (!((_c = font.opentypeFeatures) == null ? void 0 : _c.chars)) {
|
|
4077
|
+
setMessage(`Error: No OpenType feature data found in "${font.title || "this font"}". Generate font data first.`);
|
|
4078
|
+
setTimeout(() => setMessage(""), 5e3);
|
|
4079
|
+
return;
|
|
4080
|
+
}
|
|
4081
|
+
const features = [];
|
|
4082
|
+
Object.keys(value).forEach((key) => {
|
|
4083
|
+
var _a3;
|
|
4084
|
+
if (key !== "features" && ((_a3 = value[key]) == null ? void 0 : _a3.feature)) {
|
|
4085
|
+
const requiredFeatures = value[key].feature.split(" ");
|
|
4086
|
+
const approved = requiredFeatures.every((v) => font.opentypeFeatures.chars.includes(v));
|
|
4087
|
+
if (approved) features.push(key);
|
|
4088
|
+
}
|
|
4089
|
+
});
|
|
4090
|
+
onChange((0, import_sanity13.set)({ ...value, features }));
|
|
4091
|
+
setMessage(`Features detected: ${features.length ? features.join(", ") : "none"}.`);
|
|
4092
|
+
setTimeout(() => setMessage(""), 5e3);
|
|
4093
|
+
} catch (err) {
|
|
4094
|
+
setMessage("Error detecting features. Check the console for details.");
|
|
4095
|
+
console.error("SetOTF detect error:", err);
|
|
4096
|
+
}
|
|
4097
|
+
};
|
|
4098
|
+
return /* @__PURE__ */ React.createElement(import_ui15.Stack, { className: "openType" }, ((_a = value == null ? void 0 : value.features) == null ? void 0 : _a.length) > 0 && /* @__PURE__ */ React.createElement(import_ui15.Text, { muted: true, size: 1, style: { marginBottom: "0.5rem" } }, "Number of features: ", value.features.length), !!((_b = stylesObject == null ? void 0 : stylesObject.fonts) == null ? void 0 : _b.length) && /* @__PURE__ */ React.createElement(
|
|
4099
|
+
import_ui15.Button,
|
|
4100
|
+
{
|
|
4101
|
+
text: "Detect OTF",
|
|
4102
|
+
mode: "ghost",
|
|
4103
|
+
onClick: detect,
|
|
4104
|
+
style: { borderRadius: "0 3px 0 0", marginBottom: "1rem" }
|
|
4105
|
+
}
|
|
4106
|
+
), !!message && /* @__PURE__ */ React.createElement(import_ui15.Text, { muted: true, size: 1 }, /* @__PURE__ */ React.createElement("br", null), message, /* @__PURE__ */ React.createElement("br", null), /* @__PURE__ */ React.createElement("br", null)), props.renderDefault(props));
|
|
4107
|
+
};
|
|
4108
|
+
|
|
4109
|
+
// src/components/StyleCountInput.jsx
|
|
4110
|
+
var import_react18 = __toESM(require("react"));
|
|
4111
|
+
var import_ui16 = require("@sanity/ui");
|
|
4112
|
+
var import_sanity14 = require("sanity");
|
|
4113
|
+
var StyleCountInput = (props) => {
|
|
4114
|
+
const styles = (0, import_sanity14.useFormValue)(["styles", "fonts"]) || [];
|
|
4115
|
+
const vfStyles = (0, import_sanity14.useFormValue)(["styles", "variableFont"]) || [];
|
|
4116
|
+
const count = styles.length + vfStyles.length;
|
|
4117
|
+
return /* @__PURE__ */ import_react18.default.createElement(import_ui16.Text, { size: 1 }, count);
|
|
4118
|
+
};
|
|
4119
|
+
|
|
3967
4120
|
// src/utils/getEmptyFontKit.js
|
|
3968
4121
|
var fontkit7 = __toESM(require("fontkit"));
|
|
3969
4122
|
var import_slugify4 = __toESM(require("slugify"));
|
|
@@ -4050,11 +4203,14 @@ var readFontFile2 = (file) => {
|
|
|
4050
4203
|
KeyValueInput,
|
|
4051
4204
|
KeyValueReferenceInput,
|
|
4052
4205
|
PriceInput,
|
|
4206
|
+
PrimaryCollectionGeneratorTypeface,
|
|
4053
4207
|
RegenerateSubfamiliesComponent,
|
|
4054
4208
|
SCRIPTS,
|
|
4055
4209
|
SCRIPTS_OBJECT,
|
|
4210
|
+
SetOTF,
|
|
4056
4211
|
SingleUploaderTool,
|
|
4057
4212
|
StatusDisplay,
|
|
4213
|
+
StyleCountInput,
|
|
4058
4214
|
UpdateScriptsComponent,
|
|
4059
4215
|
UploadButton,
|
|
4060
4216
|
UploadScriptsComponent,
|