import { BiteStyleProps, StrategiesProps, InteractionProps, currentResponsiveProps, } from '@components/DesignPanel/types'; import isArray from 'lodash/isArray'; import { splitOnLast } from '@utils/strings'; import { mapOptions } from '@tailwind/helpers'; import has from 'lodash/has'; export const getThemeOptions = (option: string) => { const theme = wp.data.select('biteStore/editor').getTheme(); if (!has(theme, option)) { return []; } // Remove empty theme values so we don't generate empty options theme.colors = theme.colors.filter((c: any) => c?.value !== ''); theme.fonts = theme.fonts.filter((f: any) => f?.value !== ''); theme.fontSizes = theme.fontSizes.filter((f: any) => f?.value !== ''); switch (option) { case 'colors': const themeColors = mapOptions( theme.colors.map((c: any) => ({ id: c?.token ? c?.token : '', label: c?.name || '', value: c.value, })) ); return themeColors; case 'fonts': const themeFontFamilies = mapOptions( theme.fonts.map((f: any) => ({ id: f?.token || '', label: f?.name || '', value: f?.value || '', })) ); return themeFontFamilies; case 'fontSizes': const themeFontSizes = mapOptions( theme.fontSizes.map((f: any) => ({ id: f?.token || '', label: f?.name || '', value: f?.value || '', })) ); return themeFontSizes; default: return []; } }; // Get the matching design option based on filter criteria export const getModifierOption = ( currentModifierId: string, currentResponsive: string, currentInteraction: string, currentDesign: BiteStyleProps[], options: OptionProps[] ) => { return ( currentDesign.find( (item: BiteStyleProps) => options.some((option: OptionProps) => option.value === item.value) && item.id === currentModifierId && item.screen === currentResponsive && (item.interaction === currentInteraction || (!item.interaction && !currentInteraction)) ) || false ); }; // create a list of complete design options out of simple options with only a value and id export const createCompleteOptions = ( options: { id: string; value: string; }[], currentResponsive: currentResponsiveProps, currentInteraction: InteractionProps, currentStrategies: StrategiesProps ) => { const completeOptions = [] as BiteStyleProps[]; options.map((option) => { completeOptions.push({ id: option.id, value: option.value, screen: currentResponsive, interaction: currentInteraction, strategies: currentStrategies, }); }); return completeOptions; }; // Find the current design option and its index export const getDesignOption = ( value: string, currentModifierId: string, store: any ) => { const currentOption = store.currentDesign.find( (item: BiteStyleProps) => item.value === value && item.id === currentModifierId && item.screen === store.currentResponsive && (item.interaction === store.interaction || !item.interaction) ); return { current: currentOption, index: store.currentDesign.indexOf(currentOption), }; }; // Update the design list with the new option export const getNewDesignList = ( value: string, currentModifierId: string, store: any ): BiteStyleProps[] => { const designList = [...store.currentDesign]; const updatedOption: BiteStyleProps = { id: store.currentModifierId, value: value, screen: store.currentResponsive, interaction: store.currentInteraction as InteractionProps, strategies: store.currentStrategies as StrategiesProps, }; const { current, index } = getDesignOption(value, currentModifierId, store); if (!current) { designList.push(updatedOption); } else if (!value) { designList.splice(index, 1); } else if (current.value !== value) { designList[index] = updatedOption; } return designList; }; export const getMergedDesignList = ( oldList = [] as BiteStyleProps[], newList = [] as BiteStyleProps[] ) => { // Create a map to track uniqueness of items by their id, screen, and interaction const itemMap: Record = {}; // Add items from oldList to the map oldList.forEach((oldItem) => { const key = `${oldItem.id}-${oldItem.screen}-${oldItem.interaction || ''}`; if (!itemMap[key]) { itemMap[key] = oldItem; } }); // Process items from newList newList.forEach((newItem) => { const key = `${newItem.id}-${newItem.screen}-${newItem.interaction || ''}`; if (newItem.value === '') { // If the newItem has an empty value, remove it from the map (override the oldItem) itemMap[key] = null; } else { // Otherwise, add or update the map with the newItem itemMap[key] = newItem; } }); // Build the final syncedList, excluding null entries const syncedList = Object.values(itemMap).filter( (item) => item !== null ) as BiteStyleProps[]; return syncedList; }; export const getNewDesignClasses = (designList: BiteStyleProps[]): string => { let designClass = ''; designList.forEach((biteStyle: BiteStyleProps) => { const modifier = blockbite.codex.modifiers.find( (modifier: any) => modifier.id === biteStyle.id ); if (!modifier) return; if (!isArray(biteStyle.strategies)) { biteStyle.strategies = []; } // blockbite responsive prefixes const screen = biteStyle.screen === 'all' ? '' : `b_${biteStyle.screen}:`; const selector = getSelector(modifier, biteStyle); const interaction = getInteraction(biteStyle.interaction); const { value } = getValue(biteStyle); const { children, important } = getStrategies(biteStyle.strategies); designClass += `${screen}${children}${interaction}${important}${selector}${value} `; }); return designClass; }; const getInteraction = (interaction: InteractionProps) => { if (!interaction) return ''; const interactions = { hover: 'hover:', focus: 'focus:', active: 'active:', b_active: 'b_active:', 'b_active-child': 'b_active-child:', marker: 'marker:', odd: 'odd:', even: 'even:', } as any; const interaction_pick = interactions[interaction] ? interactions[interaction] : ''; return interaction_pick; }; const getStrategies = (strategies: any) => { const options = ['children', 'important', 'odd', 'even']; const prefixes: { [key: string]: string } = { children: '*:', important: '!', } as any; return options.reduce((acc, option) => { acc[option] = strategies.includes(option) ? prefixes[option] : ''; return acc; }, {} as any); }; const getValue = (biteStyle: BiteStyleProps) => { let value = biteStyle.value; // const exludedModifiers = ['flextype', 'flexdirection']; // const excludeClass = exludedModifiers.includes(biteStyle.id); const excludeClass = false; return { value, excludeClass }; }; /** * * @param modifier * @param biteStyle * @param currentDesign * @returns Current Selector, with possible dynamic selectors * for example if flex-direction is in biteStyle, we can replace justify-center with items-center */ const getSelector = (modifier: any, biteStyle: BiteStyleProps): string => { if (!modifier) return ''; if (biteStyle.value === '') return ''; let finalSelector = ''; // Fallback to static selector if no dynamic selector was applied if (!finalSelector) { finalSelector = modifier.selector ? modifier.selector : ''; } // Return the final selector concatenated with the modifier's value return finalSelector; }; export const mergeModifierPanels = (currentDesign: any, panels: any) => { if (!currentDesign || !panels) return []; const newList = currentDesign.map((design: any) => { let modifier; for (const panel of panels) { for (const control of panel.controls) { modifier = control.modifiers.find((mod: any) => mod.id === design.id); if (modifier) { modifier.panelId = panel.id; modifier.panelControls = panel.controls; break; } } if (modifier) break; } return { ...modifier, ...design }; }); return newList; }; export const getValueUnit = (value: string) => { // Convert value to string if it's not already a string value = String(value); if (!value) return { foundUnit: 'arbitrary', foundValue: '' }; let foundUnit = ''; let foundValue = value; if (value.includes('b_screen')) { foundUnit = 'screen'; } else if (value.includes('b_clamp')) { foundUnit = 'clamp'; } else if (value.includes('b_')) { foundUnit = 'grid'; } else if (value.includes('/') || value.includes('full')) { foundUnit = 'percentage'; } else if (value.includes('[') && value.includes(']')) { foundUnit = 'arbitrary'; } else { foundUnit = 'native'; } if (foundUnit === 'arbitrary' && value?.includes('px]')) { // remove [ from arbitrary value and px] from arbitrary value foundValue = value.replace('[', '').replace('px]', ''); } else if (foundUnit === 'arbitrary') { foundValue = value.replace('[', '').replace(']', ''); } return { foundUnit, foundValue }; }; export const formatUnit = (value: any, unit: string, ext: string = 'px') => unit === 'arbitrary' && value ? `[${value}${ext}]` : value; const flexgridModifiers = [ 'flextype', 'flexdirection', 'flexalignjustify', 'flexalignitems', 'gridcols', 'gridrows', 'gridarea', 'gridjustifyitems', 'gridalignitems', 'flexgapa', 'flexgapx', 'flexgapy', 'flexwidth', 'flexheight', 'flexposition', 'spacex', 'spacey', 'zindex', ]; type OptionProps = { value: string; label: string; }; export function getStylesObjectsFromTailwindClasses(tailwindClasses: string) { // bite styles are an array of type: // [ // { // "id": "pa", // "value": "6", // "screen": "all", // "interaction": "", // "strategies": [] // } // ] const biteStyle = [] as any; const flexStyle = [] as any; const allModifiers = blockbite.codex.modifiers; const biteStyleModifiers = allModifiers.filter( (modifier: any) => !flexgridModifiers.includes(modifier.id) ); const flexStyleModifiers = allModifiers.filter((modifier: any) => flexgridModifiers.includes(modifier.id) ) as any; // iterate over all the classes tailwindClasses.split(' ').forEach((tailwindClass) => { // split the class into parts const [firstPart] = splitOnLast(tailwindClass, '-'); // find the modifier id const biteStyleModifier = biteStyleModifiers.find((modifier: any) => { // Should match selectors for classes like text-red-900, border-l-2, as well as p-4 if (modifier.selector == null) { return (modifier.options as OptionProps[]).filter( (option) => option.value === tailwindClass ).length; } return `${firstPart}-`.startsWith(modifier.selector); }) as any; const flexStyleModifier = flexStyleModifiers.find((modifier: any) => { if (modifier.selector == null) { return (modifier.options as OptionProps[]).filter( (option) => option.value === tailwindClass ).length; } return `${firstPart}-`.startsWith(modifier.selector); }); if (biteStyleModifier) { biteStyle.push({ id: biteStyleModifier.id, value: tailwindClass.replace(biteStyleModifier.selector || '', ''), screen: 'all', interaction: '', strategies: [], }); return; } if (flexStyleModifier) { flexStyle.push({ id: flexStyleModifier.id, value: tailwindClass.replace(flexStyleModifier.selector || '', ''), screen: 'all', interaction: '', strategies: [], }); } }); return { biteStyle, flexStyle }; } export default { createCompleteOptions, getValueUnit, getThemeOptions, getDesignOption, getModifierOption, getNewDesignClasses, mergeModifierPanels, formatUnit, };