ldc-groupe/resources/ts/hooks/use-string.tsx
2026-04-16 12:13:28 +02:00

246 lines
6.4 KiB
TypeScript

// * Packages
import slugify from "slugify";
import pluralize, { singular } from "pluralize";
interface Stringable {
/**
* Trim a word based.
* Examples:
* - "...Some.$.in.the.GARDEN !..." -> Some $ in the GARDEN !
*
* @return Stringable
*/
trim: () => Stringable;
/**
* Slugify a word based.
* Examples:
* - Some $ in the GARDEN ! -> some-dollar-in-the-garden
*
* @return Stringable
*/
slug: () => Stringable;
/**
* Upper case the first letter of a word based.
* Examples:
* - some $ in the GARDEN ! -> Some $ in the GARDEN !
*
* @return Stringable
*/
ucFirst: () => Stringable;
/**
* Camel case a word based.
* Examples:
* - Some $ in the GARDEN ! -> some$InTheGarden!
*
* @return Stringable
*/
camelCase: () => Stringable;
/**
* Pascal case a word based.
* Examples:
* - some $ in the GARDEN ! -> Some$InTheGarden!
*
* @return Stringable
*/
pascalCase: () => Stringable;
/**
* Kebab case a word based.
* Examples:
* - Some $ in the GARDEN ! -> some-$-in-the-garden-!
*
* @return Stringable
*/
kebabCase: () => Stringable;
/**
* Snake case a word based.
* Examples:
* - Some $ in the GARDEN ! -> some_$_in_the_garden_!
*
* @return Stringable
*/
snakeCase: () => Stringable;
/**
* Upper case a word based.
* Examples:
* - Some $ in the GARDEN ! -> SOME $ IN THE GARDEN !
*
* @return Stringable
*/
upperCase: () => Stringable;
/**
* Lower case a word based.
* Examples:
* - Some $ in the GARDEN ! -> some $ in the garden !
*
* @return Stringable
*/
lowerCase: () => Stringable;
/**
* Pluralize a word based.
* Examples:
* - word -> words
* - story -> stories
*
* @return Stringable
*/
plural: () => Stringable;
/**
* Singularize a word based.
* Examples:
* - words -> word
* - stories -> story
*
* @return Stringable
*/
singular: () => Stringable;
/**
* Return the transformated string.
*
* @return string
*/
value: () => string;
}
/**
* Get a Stringable object.
*
* @param string initialValue
* @return Stringable
*/
const str = (initialValue: string): Stringable => {
let value = initialValue;
// * HELPERS
/**
* Split string into multiple segments.
* @param string string
* @returns string[]
*/
const splitIntoSegments = (string: string): string[] =>
string.split(/[-_\s]+/).filter(Boolean);
// * METHODS
return {
trim: (): Stringable => {
value = value.trim();
return str(value);
},
slug: (): Stringable => {
value = slugify(value, { lower: true, strict: true, trim: true });
return str(value);
},
ucFirst: (): Stringable => {
value = value.charAt(0).toUpperCase() + value.slice(1);
return str(value);
},
camelCase: (): Stringable => {
const segments = splitIntoSegments(value);
// If segments are empty.
if (segments.length === 0) {
value = "";
return str(value);
}
// If single segment and already in camel case.
if (segments.length === 1 && /^[a-z][a-zA-Z0-9]*$/.test(value)) {
return str(value);
}
// first segment: lower first char, preserve internals
const first =
segments[0].charAt(0).toLowerCase() + segments[0].slice(1);
// rest: uppercase first char; preserve internals if mixed case, otherwise lowercase internals
const rest = segments.slice(1).map((p) => {
if (!p) return "";
const hasLower = /[a-z]/.test(p);
const hasUpper = /[A-Z]/.test(p);
const isMixed = hasLower && hasUpper;
const head = p.charAt(0).toUpperCase();
const tail = p.slice(1);
return isMixed ? head + tail : head + tail.toLowerCase();
});
value = [first, ...rest].join("");
return str(value);
},
pascalCase: (): Stringable => {
const segments = splitIntoSegments(value);
if (segments.length === 0) {
value = "";
return str(value);
}
// If already Pascal-ish, keep as is
if (segments.length === 1 && /^[A-Z][a-zA-Z0-9]*$/.test(value)) {
return str(value);
}
const converted = segments.map((p) => {
const hasLower = /[a-z]/.test(p);
const hasUpper = /[A-Z]/.test(p);
const isMixed = hasLower && hasUpper;
if (isMixed) {
// preserve internal capitals, only uppercase first char
return p.charAt(0).toUpperCase() + p.slice(1);
}
// normalize (capitalize first, lowercase rest)
return p.charAt(0).toUpperCase() + p.slice(1).toLowerCase();
});
value = converted.join("");
return str(value);
},
kebabCase: (): Stringable => {
const words = value
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
.split(/[-_\s]+/)
.filter(Boolean);
value = words.map((w) => w.toLowerCase()).join("-");
return str(value);
},
snakeCase: (): Stringable => {
const words = value
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
.split(/[-\s]+|_+/)
.filter(Boolean);
value = words.map((w) => w.toLowerCase()).join("_");
return str(value);
},
upperCase: (): Stringable => {
value = value.toUpperCase();
return str(value);
},
lowerCase: (): Stringable => {
value = value.toLowerCase();
return str(value);
},
plural: (): Stringable => {
value = pluralize(value);
return str(value);
},
singular: (): Stringable => {
value = singular(value);
return str(value);
},
value: (): string => value,
};
};
export default str;