diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index 9124be46c..9306988be 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -5,14 +5,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "./*": { - "types": "./dist/*.d.ts", - "default": "./dist/*.js" - } + "styles": "./src/styles/styles.css" }, "scripts": { "prebuild": "tsx scripts/validate-env.ts", @@ -29,6 +22,9 @@ "@buster/server-shared": "workspace:*", "@buster/typescript-config": "workspace:*", "@buster/vitest-config": "workspace:*", + "@tailwindcss/postcss": "4.1.11", + "@tailwindcss/vite": "^4.1.11", + "@vitejs/plugin-react": "^4.7.0", "chart.js": "4.5.0", "chartjs-adapter-dayjs-4": "^1.0.4", "chartjs-plugin-annotation": "^3.1.0", @@ -41,7 +37,8 @@ "lodash": "^4.17.21", "react": "^18.0.0", "tailwind-merge": "^3.3.1", - "tailwindcss": "4.1.11" + "tailwindcss": "4.1.11", + "vite": "^7.0.5" }, "devDependencies": { "@types/lodash": "^4.17.20", diff --git a/packages/ui-components/src/components/charts/BusterChartJS/ChartJSTheme.ts b/packages/ui-components/src/components/charts/BusterChartJS/ChartJSTheme.ts index 720f5ad0d..45c618399 100644 --- a/packages/ui-components/src/components/charts/BusterChartJS/ChartJSTheme.ts +++ b/packages/ui-components/src/components/charts/BusterChartJS/ChartJSTheme.ts @@ -1,6 +1,8 @@ 'use client'; -import { isServer } from '@tanstack/react-query'; +import { truncateText } from '@/lib/text'; +import { isServer } from '@/lib/window'; +import { DEFAULT_CHART_THEME } from '@buster/server-shared/metrics'; import { ArcElement, BarController, @@ -11,9 +13,9 @@ import { Colors, DoughnutController, Legend, - LinearScale, LineController, LineElement, + LinearScale, LogarithmicScale, PieController, PointElement, @@ -21,13 +23,11 @@ import { TimeScale, TimeSeriesScale, Title, - Tooltip + Tooltip, } from 'chart.js'; import ChartJsAnnotationPlugin from 'chartjs-plugin-annotation'; import ChartDataLabels from 'chartjs-plugin-datalabels'; import ChartDeferred from 'chartjs-plugin-deferred'; -import { DEFAULT_CHART_THEME } from '@buster/server-shared/metrics'; -import { truncateText } from '@/lib/text'; import { ChartMountedPlugin } from './core/plugins'; import ChartTrendlinePlugin from './core/plugins/chartjs-plugin-trendlines'; import './core/plugins/chartjs-plugin-dayjs'; @@ -85,7 +85,7 @@ ChartJS.defaults.font = { ...ChartJS.defaults.font, family: chartJSThemefontFamily, size: 11, - weight: 'normal' + weight: 'normal', }; [ @@ -93,21 +93,21 @@ ChartJS.defaults.font = { ChartJS.defaults.scales.linear, ChartJS.defaults.scales.logarithmic, ChartJS.defaults.scales.time, - ChartJS.defaults.scales.timeseries + ChartJS.defaults.scales.timeseries, ].forEach((scale) => { scale.title = { ...scale.title, font: { ...ChartJS.defaults.font, - size: 10 - } + size: 10, + }, }; }); [ ChartJS.defaults.scales.category, ChartJS.defaults.scales.time, - ChartJS.defaults.scales.timeseries + ChartJS.defaults.scales.timeseries, ].forEach((scale) => { scale.ticks.showLabelBackdrop = true; scale.ticks.z = 10; @@ -115,14 +115,14 @@ ChartJS.defaults.font = { scale.ticks.backdropColor = chartJSThemebackgroundColor; scale.ticks.autoSkipPadding = 4; scale.ticks.align = 'center'; - scale.ticks.callback = function (value, index, values) { + scale.ticks.callback = function (value) { return truncateText(this.getLabelForValue(value as number), 18); }; }); for (const scale of [ ChartJS.defaults.scales.category, ChartJS.defaults.scales.linear, - ChartJS.defaults.scales.logarithmic + ChartJS.defaults.scales.logarithmic, ]) { scale.ticks.z = 0; //this used to be a 100, but I changed it for datalabels sake scale.ticks.backdropColor = chartJSThemebackgroundColor; @@ -136,13 +136,13 @@ export const DEFAULT_CHART_LAYOUT = { top: 14, bottom: 4, left: 10, - right: 10 - } + right: 10, + }, }; ChartJS.defaults.layout = { ...ChartJS.defaults.layout, - ...DEFAULT_CHART_LAYOUT + ...DEFAULT_CHART_LAYOUT, }; ChartJS.defaults.normalized = true; @@ -150,7 +150,7 @@ ChartJS.defaults.plugins = { ...ChartJS.defaults.plugins, legend: { ...ChartJS.defaults.plugins.legend, - display: false + display: false, }, datalabels: { ...ChartJS.defaults.plugins.datalabels, @@ -159,12 +159,12 @@ ChartJS.defaults.plugins = { font: { weight: 'normal', size: 10, - family: chartJSThemefontFamily - } + family: chartJSThemefontFamily, + }, }, tooltipHoverBar: { - isDarkMode: false - } + isDarkMode: false, + }, }; //PIE SPECIFIC @@ -173,7 +173,7 @@ ChartJS.overrides.pie = { hoverBorderColor: 'white', layout: { autoPadding: true, - padding: 35 + padding: 35, }, elements: { ...ChartJS.overrides.pie?.elements, @@ -183,9 +183,9 @@ ChartJS.overrides.pie = { borderRadius: 5, borderWidth: 2.5, borderAlign: 'center', - borderJoinStyle: 'round' - } - } + borderJoinStyle: 'round', + }, + }, }; //BAR SPECIFIC @@ -195,9 +195,9 @@ ChartJS.overrides.bar = { ...ChartJS.overrides.bar?.elements, bar: { ...ChartJS.overrides.bar?.elements?.bar, - borderRadius: 4 - } - } + borderRadius: 4, + }, + }, }; //LINE SPECIFIC @@ -207,7 +207,7 @@ ChartJS.overrides.line = { ...ChartJS.overrides.line?.elements, line: { ...ChartJS.overrides.line?.elements?.line, - borderWidth: 2 - } - } + borderWidth: 2, + }, + }, }; diff --git a/packages/ui-components/src/lib/date.ts b/packages/ui-components/src/lib/date.ts index 28113bac6..86dcecb9d 100644 --- a/packages/ui-components/src/lib/date.ts +++ b/packages/ui-components/src/lib/date.ts @@ -15,7 +15,6 @@ import isDate from 'lodash/isDate'; import lodashIsNaN from 'lodash/isNaN'; import isNumber from 'lodash/isNumber'; import isString from 'lodash/isString'; -import { getBrowserLanguage } from './language'; import { isNumeric } from './numbers'; dayjs.extend(relativeTime); @@ -25,106 +24,23 @@ dayjs.extend(utc); dayjs.extend(timezone); dayjs.extend(quarterOfYear); -export const getNow = () => dayjs(); export const createDayjsDate = (date: string | Date) => dayjs(date); -const KEY_DATE_FORMATS = [ - 'date', - 'year', - 'month', - 'day', - 'timestamp', - 'time_stamp', - 'created_at', - '_month', -]; -const KEY_DATE_INCLUDE_FORMATS = ['_month', '_year']; -const VALID_DATE_FORMATS = [ - 'YYYY-MM-DD', - 'YYYY-MM', - 'YYYY-MM-DDTHH:mm:ss', - 'YYYY-MM-DD HH:mm:ss', - 'YYYY-MM-DDTHH:mm:ss.sssZ', - 'YYYY-MM-DDTHH:mm:ssZ', - 'YYYY-MM-DD HH:mm:ssZ', - 'YYYY-MM-DD HH:mm:ss.sssZ', - 'YYYY-MM-DD HH:mm:ss.sss', - 'YYYY-MM-DDTHH:mm:ss.sss', - 'YYYY-MM-DDTHH:mm:ssZ', - 'YYYY-MM-DDTHH:mm:ss', - 'YYYY-MM-DD HH:mm:ss', - 'YYYY-MM-DD HH:mm', - 'YYYY-MM-DDTHH:mm', - 'YYYY-MM-DD HH', - 'YYYY-MM-DD HH:mm:ss.SSS', - 'YYYY-MM-DDTHH:mm:ss.SSS', - 'YYYY-MM-DDTHH:mm:ss.SSSZ', - 'YYYY-MM-DD HH:mm:ss.SSSZ', - 'YYYY-MM-DDTHH:mm:ss.SSS', - 'MM DD YYYY', - 'M D YYYY', - 'MMM D, YYYY', - 'MMMM D, YYYY', -]; - -export const numberDateFallback = ( - date: string | number | Date, - dateKey?: string, - convertNumberTo?: ColumnLabelFormat['convertNumberTo'] -) => { - if (convertNumberTo === 'day_of_week' && isNumber(date) && valueIsValidDayOfWeek(date, dateKey)) { - return dayjs().day(Number(date)).startOf('day'); - } - if (valueIsValidMonth(date, dateKey) || convertNumberTo === 'month_of_year') { - return dayjs() - .month(Number(date) - 1) - .startOf('month') - .startOf('day'); - } - - const numericDate = Number(date); - if (!lodashIsNaN(numericDate)) { - // Check for 13 digit timestamp (milliseconds) - if (String(numericDate).length === 13) { - return dayjs(numericDate); - } - // Check for 10 digit timestamp (seconds) - if (String(numericDate).length === 10) { - return dayjs.unix(numericDate); - } - } - - return String(date); -}; - -const extractDateForFormatting = ( - date: string | number | Date, - dateKey?: string, - convertNumberTo?: ColumnLabelFormat['convertNumberTo'] -) => { - if (isString(date)) return date; - if (isNumber(date)) return numberDateFallback(date, dateKey, convertNumberTo); - if (isDate(date)) return new Date(date); - return String(date); -}; - export const formatDate = ({ date, format, isUTC = false, - dateKey, ignoreValidation = true, convertNumberTo = null, }: { date: string | number | Date; format: string; isUTC?: boolean; - dateKey?: string; ignoreValidation?: boolean; convertNumberTo?: ColumnLabelFormat['convertNumberTo']; }): string => { try { - const myDate = extractDateForFormatting(date, dateKey, convertNumberTo); + const myDate = extractDateForFormatting(date, convertNumberTo); const valid = ignoreValidation ? true @@ -171,77 +87,98 @@ export const formatDate = ({ } }; -export const isDateSame = ({ - date, - compareDate, - interval = 'day', -}: { - date: string | number | dayjs.Dayjs | Date; - compareDate: string | number | dayjs.Dayjs | Date; - interval?: dayjs.OpUnitType; -}) => { - return dayjs(date).isSame(compareDate, interval); +const numberDateFallback = ( + date: string | number | Date, + convertNumberTo?: ColumnLabelFormat['convertNumberTo'] +) => { + if (convertNumberTo === 'day_of_week' && isNumber(date) && valueIsValidDayOfWeek(date)) { + return dayjs().day(Number(date)).startOf('day'); + } + if (valueIsValidMonth(date) || convertNumberTo === 'month_of_year') { + return dayjs() + .month(Number(date) - 1) + .startOf('month') + .startOf('day'); + } + + const numericDate = Number(date); + if (!lodashIsNaN(numericDate)) { + // Check for 13 digit timestamp (milliseconds) + if (String(numericDate).length === 13) { + return dayjs(numericDate); + } + // Check for 10 digit timestamp (seconds) + if (String(numericDate).length === 10) { + return dayjs.unix(numericDate); + } + } + + return String(date); }; -export const isDateBefore = ({ - date, - compareDate, - interval = 'day', -}: { - date: string | number | dayjs.Dayjs | Date; - compareDate: string | number | dayjs.Dayjs | Date; - interval?: dayjs.OpUnitType; -}) => { - return dayjs(date).isBefore(compareDate, interval); +const extractDateForFormatting = ( + date: string | number | Date, + + convertNumberTo?: ColumnLabelFormat['convertNumberTo'] +) => { + if (isString(date)) return date; + if (isNumber(date)) return numberDateFallback(date, convertNumberTo); + if (isDate(date)) return new Date(date); + return String(date); }; -export const isDateAfter = ({ - date, - compareDate, - interval = 'day', -}: { - date: string | number | dayjs.Dayjs | Date; - compareDate: string | number | dayjs.Dayjs | Date; - interval?: dayjs.OpUnitType; -}) => { - return dayjs(date).isAfter(compareDate, interval); -}; - -export const timeFromNow = (date: string | Date, relative = false) => { - return dayjs(new Date(date)).fromNow(relative); -}; - -export const valueIsValidMonth = (value: string | number | Date | undefined, key?: string) => { +const valueIsValidMonth = (value: string | number | Date | undefined) => { if (value === undefined || value === null) return false; const month = Number(value); - return (month > 0 && month <= 12) || isKeyLikelyDate(key); + return month > 0 && month <= 12; }; -const valueIsValidDayOfWeek = (value: string | number | Date | undefined, key?: string) => { +const valueIsValidDayOfWeek = (value: string | number | Date | undefined) => { if (value === undefined || value === null) return false; const day = Number(value); - return (day >= 0 && day <= 7) || isKeyLikelyDate(key); + return day >= 0 && day <= 7; }; -const isKeyLikelyDate = (dateKey?: string) => { - return ( - KEY_DATE_FORMATS.some((v) => v === dateKey) || - KEY_DATE_INCLUDE_FORMATS.some((v) => dateKey?.toLowerCase().endsWith(v)) - ); -}; +// const VALID_DATE_FORMATS = [ +// 'YYYY-MM-DD', +// 'YYYY-MM', +// 'YYYY-MM-DDTHH:mm:ss', +// 'YYYY-MM-DD HH:mm:ss', +// 'YYYY-MM-DDTHH:mm:ss.sssZ', +// 'YYYY-MM-DDTHH:mm:ssZ', +// 'YYYY-MM-DD HH:mm:ssZ', +// 'YYYY-MM-DD HH:mm:ss.sssZ', +// 'YYYY-MM-DD HH:mm:ss.sss', +// 'YYYY-MM-DDTHH:mm:ss.sss', +// 'YYYY-MM-DDTHH:mm:ssZ', +// 'YYYY-MM-DDTHH:mm:ss', +// 'YYYY-MM-DD HH:mm:ss', +// 'YYYY-MM-DD HH:mm', +// 'YYYY-MM-DDTHH:mm', +// 'YYYY-MM-DD HH', +// 'YYYY-MM-DD HH:mm:ss.SSS', +// 'YYYY-MM-DDTHH:mm:ss.SSS', +// 'YYYY-MM-DDTHH:mm:ss.SSSZ', +// 'YYYY-MM-DD HH:mm:ss.SSSZ', +// 'YYYY-MM-DDTHH:mm:ss.SSS', +// 'MM DD YYYY', +// 'M D YYYY', +// 'MMM D, YYYY', +// 'MMMM D, YYYY', +// ]; -export const isDateValid = ({ +const isDateValid = ({ date, - dateKey, + useNumbersAsDateKey = true, }: { date: string | number | Date | undefined; - dateKey?: string; + useNumbersAsDateKey?: boolean; }) => { if (isDate(date)) return true; - if (useNumbersAsDateKey && dateKey && isNumeric(date as string) && isKeyLikelyDate(dateKey)) { - return valueIsValidMonth(date || '', dateKey); + if (useNumbersAsDateKey && isNumeric(date as string)) { + return valueIsValidMonth(date || ''); } if (!date || isNumber(date)) return false; const hyphenCount = (date.match(/-/g) || []).length; @@ -260,114 +197,7 @@ export const isDateValid = ({ if (filter) return filter && dayjs(date).isValid(); - return VALID_DATE_FORMATS.some((format) => dayjs(date, format, true).isValid()); -}; - -export const keysWithDate = ( - data: Record[] = [], - params?: { - parseable?: boolean; - includeFormats?: boolean; - absoluteFormats?: boolean; - } -): string[] => { - const uniqueKeys = Object.keys(data[0] || {}); - const { parseable, includeFormats = true, absoluteFormats = true } = params || {}; - - if (parseable) { - return uniqueKeys.filter((key) => { - const value = data?.[0]?.[key]; - return isDateValid({ - date: String(value), - dateKey: key, - }); - }); - } - - return uniqueKeys - .filter((key) => { - const value = data?.[0]?.[key]; - - return ( - (absoluteFormats && KEY_DATE_FORMATS.some((keyDate) => keyDate === key.toLowerCase())) || - isDateValid({ - date: String(value), - dateKey: key, - }) || - (includeFormats && - KEY_DATE_INCLUDE_FORMATS.some((format) => key.toLowerCase().endsWith(format))) - ); - }) - .sort((v) => { - if (v.toLowerCase().includes('year')) return 1; - return -1; - }); -}; - -export const formatTime = (date: string | Date, format: string, isUTC: boolean) => { - return isUTC ? dayjs.utc(date).format(format) : dayjs(date).format(format || 'h:mm A'); -}; - -export enum DEFAULT_TIME_ENCODE_FORMATS { - MONTHS = 'month', - YEARS = 'year', - DAYS = 'day', - SECONDS = 'second', - HOURS = 'hour', - MONTHS_ONLY = 'month_only', - NO_FORMAT = 'no_format', - MILLISECONDS = 'millisecond', - WEEK = 'week', - MINUTES = 'minute', -} - -let loadedLocales: string[] = []; -export const setNewDateLocale = async (locale: string) => { - if (!locale) return; - let _locale: string = locale; - - const loadAndSet = async (_locale: string) => { - try { - await import(`dayjs/locale/${_locale}.js`); - loadedLocales = [...loadedLocales, _locale]; - } catch (_error) { - // - } - }; - - try { - if (!loadedLocales.includes(_locale)) { - if (_locale === 'en') _locale = getBrowserLanguage(true).toLocaleLowerCase(); - loadAndSet(_locale); - } - } catch { - try { - _locale = locale; - loadAndSet(_locale); - } catch (error) { - console.error(`Error loading locale ${_locale}:`, error); - } - } - - dayjs.locale(_locale); -}; - -export const getBestDateFormat = (minDate: dayjs.Dayjs, maxDate: dayjs.Dayjs) => { - const diffInDays = maxDate.diff(minDate, 'days'); - const diffInMonths = maxDate.diff(minDate, 'months'); - const diffInYears = maxDate.diff(minDate, 'years'); - - if (diffInDays <= 1) { - return 'h:mmA'; // 1/1 2:30 PM - } - if (diffInDays <= 31) { - return 'MMM D'; // Jan 1 - } - if (diffInMonths <= 12) { - return 'MMM YYYY'; // Jan 2024 - } - if (diffInYears <= 1) { - return 'MMM YYYY'; // Jan 2024 - } - return 'YYYY'; // 2024 + return true; + + // return VALID_DATE_FORMATS.some((format) => dayjs(date, format, true).isValid()); }; diff --git a/packages/ui-components/src/lib/language.ts b/packages/ui-components/src/lib/language.ts index 3583f9f96..65fc876d7 100644 --- a/packages/ui-components/src/lib/language.ts +++ b/packages/ui-components/src/lib/language.ts @@ -1,5 +1,3 @@ -import React from 'react'; - export const getBrowserLanguage = (full = false) => { if (!navigator) return 'en'; diff --git a/packages/ui-components/src/lib/math.ts b/packages/ui-components/src/lib/math.ts index e1bc119d4..a5dadc601 100644 --- a/packages/ui-components/src/lib/math.ts +++ b/packages/ui-components/src/lib/math.ts @@ -77,77 +77,6 @@ export class JsonDataFrameOperationsSingle { } } -export class DataFrameOperations { - private data: [...(number | string)[]][]; - private columnIndex: number; - - constructor(data: [...(number | string)[]][], columnIndex = 1) { - if (!Array.isArray(data)) { - throw new Error('Data should be an array.'); - } - this.data = data; - this.columnIndex = columnIndex; - } - - first(): number { - return this.data[0]?.[this.columnIndex] as number; - } - - current(): number { - return this.data[this.data.length - 1]?.[this.columnIndex] as number; - } - - last(): number { - return this.data[this.data.length - 1]?.[this.columnIndex] as number; - } - - count(): number { - return this.data.length; - } - - sum(): number { - return this.data.reduce((acc, item) => { - const value = item[this.columnIndex]; - return acc + (Number.isNaN(value as number) ? 0 : Number(value)); - }, 0); - } - - average(): number { - const validEntries = this.data.filter( - (item) => !Number.isNaN(item[this.columnIndex] as number) - ); - return validEntries.length === 0 ? 0 : this.sum() / validEntries.length; - } - - min(): number { - return Math.min( - ...this.data - .map((item) => Number(item[this.columnIndex])) - .filter((value) => !Number.isNaN(value)) - ); - } - - max(): number { - return Math.max( - ...this.data - .map((item) => Number(item[this.columnIndex])) - .filter((value) => !Number.isNaN(value)) - ); - } - - median(): number { - const sortedValues = this.data - .map((item) => Number(item[this.columnIndex])) - .filter((value) => !Number.isNaN(value)) - .sort((a, b) => a - b); - - const middle = Math.floor(sortedValues.length / 2); - return sortedValues.length % 2 !== 0 - ? (sortedValues[middle] as number) - : ((sortedValues[middle - 1] as number) + (sortedValues[middle] as number)) / 2; - } -} - export class ArrayOperations { private data: number[] | string[] | (number | string)[]; diff --git a/packages/ui-components/src/lib/numbers.ts b/packages/ui-components/src/lib/numbers.ts index 903df2417..3b970d3ab 100644 --- a/packages/ui-components/src/lib/numbers.ts +++ b/packages/ui-components/src/lib/numbers.ts @@ -56,7 +56,7 @@ export const formatNumber = ( options?.minDecimals, options?.maximumFractionDigits, options?.maximumDecimals, - options?.maximumSignificantDigits + options?.maximumSignificantDigits, ].filter(isNumber) ); @@ -68,7 +68,7 @@ export const formatNumber = ( compactDisplay: 'short', style: options?.currency ? 'currency' : 'decimal', currency: options?.currency, - useGrouping: options?.useGrouping !== false + useGrouping: options?.useGrouping !== false, }); return formatter.format(Number(processedValue)); @@ -105,7 +105,7 @@ export const formatNumber = ( * countTotalDigits(1000000) // returns 7 * countTotalDigits(-123.4) // returns 4 (ignores sign) */ -export const countTotalDigits = (num: number, roundToDecimals: number = 2): number => { +export const countTotalDigits = (num: number, roundToDecimals = 2): number => { const roundedNum = roundNumber(num, 0, roundToDecimals); const str = Math.abs(roundedNum).toString(); return str.replace('.', '').length; diff --git a/packages/ui-components/src/lib/text.ts b/packages/ui-components/src/lib/text.ts index 74cb213d2..28f973b81 100644 --- a/packages/ui-components/src/lib/text.ts +++ b/packages/ui-components/src/lib/text.ts @@ -1,65 +1,11 @@ import isNumber from 'lodash/isNumber'; import truncate from 'lodash/truncate'; -export const inputHasText = (input: unknown): boolean => { - if (typeof input !== 'string') { - return false; - } - const trimmedInput = input.trim(); - return trimmedInput.length > 0; -}; - const capitalizeFirstLetter = (str: string): string => { if (!str) return ''; return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); }; -export const getFirstTwoCapitalizedLetters = (input: string) => { - const capitalizedLetters = input - .replace('@', '') - .replace(/[^A-Za-z]/g, '') // Remove non-alphabetic characters - .match(/[A-Z]/g); // Find all uppercase letters - - if (capitalizedLetters && capitalizedLetters.length < 2) { - return input - .replace('@', '') - .replace(/[^A-Za-z]/g, '') - .slice(0, 2) - .toUpperCase(); - } - if (capitalizedLetters && capitalizedLetters.length >= 2) { - return capitalizedLetters.slice(0, 2).filter(Boolean).join(''); - } - return ''; -}; - -export const removeAllSpaces = (str?: string) => { - return str ? str.replace(/\s/g, '') : ''; -}; - -export const getInitials = (value: string | null | undefined): string => { - if (!value) return ''; - - // Split by spaces or other common separators and filter out empty strings - const words = value.trim().split(/\s+/).filter(Boolean); - - // If we have multiple words, use the first letter of the first two words - if (words.length >= 2) { - return words - .slice(0, 2) - .map((word) => word.charAt(0).toUpperCase()) - .join(''); - } - - // If we have only one word, split by capital letters - const capitalizedLetters = value.match(/[A-Z]/g); - - if (capitalizedLetters && capitalizedLetters.length >= 2) { - return capitalizedLetters.slice(0, 2).filter(Boolean).join(''); - } - return ''; -}; - export const makeHumanReadble = (input: string | number | undefined | null): string => { if (!input && !isNumber(input)) { return ''; @@ -96,16 +42,6 @@ export const makeHumanReadble = (input: string | number | undefined | null): str return convertedString; }; -export const calculateTextWidth = (text: string, font: string): number => { - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - if (!context) return 0; - context.font = font; - const width = context.measureText(text).width; - canvas.remove(); - return width; -}; - export const truncateText = (text: string, characters: number) => { if (text.length <= characters) return text; return truncate(text, { length: characters }); diff --git a/packages/ui-components/src/lib/window.ts b/packages/ui-components/src/lib/window.ts new file mode 100644 index 000000000..df6bd5693 --- /dev/null +++ b/packages/ui-components/src/lib/window.ts @@ -0,0 +1 @@ +export const isServer = typeof window === 'undefined'; diff --git a/packages/ui-components/src/styles/styles.css b/packages/ui-components/src/styles/styles.css new file mode 100644 index 000000000..7db3ed139 --- /dev/null +++ b/packages/ui-components/src/styles/styles.css @@ -0,0 +1 @@ +@import './tailwind.css'; \ No newline at end of file diff --git a/packages/ui-components/src/styles/tailwind.css b/packages/ui-components/src/styles/tailwind.css new file mode 100644 index 000000000..f1d8c73cd --- /dev/null +++ b/packages/ui-components/src/styles/tailwind.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/packages/ui-components/vite.config.ts b/packages/ui-components/vite.config.ts new file mode 100644 index 000000000..63be2a073 --- /dev/null +++ b/packages/ui-components/vite.config.ts @@ -0,0 +1,7 @@ +import tailwindcss from '@tailwindcss/vite'; +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [react(), tailwindcss()], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ce56609c..15dba2e44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -964,6 +964,15 @@ importers: '@buster/vitest-config': specifier: workspace:* version: link:../vitest-config + '@tailwindcss/postcss': + specifier: 4.1.11 + version: 4.1.11 + '@tailwindcss/vite': + specifier: ^4.1.11 + version: 4.1.11(vite@7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) + '@vitejs/plugin-react': + specifier: ^4.7.0 + version: 4.7.0(vite@7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) chart.js: specifier: 4.5.0 version: 4.5.0 @@ -1003,6 +1012,9 @@ importers: tailwindcss: specifier: 4.1.11 version: 4.1.11 + vite: + specifier: ^7.0.5 + version: 7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) devDependencies: '@types/lodash': specifier: ^4.17.20 @@ -1879,6 +1891,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.27.1': resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==} engines: {node: '>=6.9.0'} @@ -4252,6 +4276,9 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + '@rollup/rollup-android-arm-eabi@4.44.2': resolution: {integrity: sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==} cpu: [arm] @@ -4999,6 +5026,11 @@ packages: '@tailwindcss/postcss@4.1.11': resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==} + '@tailwindcss/vite@4.1.11': + resolution: {integrity: sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + '@tanstack/form-core@1.14.2': resolution: {integrity: sha512-II/hbNxhnf5Sjhpz6i1wILGE1CHPN0OzYwz82aDGuDKchGPIdBaZcR00aduPGrBwqqo2XN9O6L/GfIvImPrlsQ==} @@ -5559,6 +5591,12 @@ packages: '@aws-sdk/credential-provider-web-identity': optional: true + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/coverage-v8@3.2.4': resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} peerDependencies: @@ -9877,6 +9915,10 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -11244,6 +11286,46 @@ packages: yaml: optional: true + vite@7.0.5: + resolution: {integrity: sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -13056,6 +13138,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 @@ -15712,6 +15804,8 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} + '@rollup/rollup-android-arm-eabi@4.44.2': optional: true @@ -16754,6 +16848,13 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.11 + '@tailwindcss/vite@4.1.11(vite@7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@tailwindcss/node': 4.1.11 + '@tailwindcss/oxide': 4.1.11 + tailwindcss: 4.1.11 + vite: 7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + '@tanstack/form-core@1.14.2': dependencies: '@tanstack/store': 0.7.2 @@ -17406,6 +17507,18 @@ snapshots: optionalDependencies: '@aws-sdk/credential-provider-web-identity': 3.840.0 + '@vitejs/plugin-react@4.7.0(vite@7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@babel/core': 7.28.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - supports-color + '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': dependencies: '@ampproject/remapping': 2.3.0 @@ -22419,6 +22532,8 @@ snapshots: react-refresh@0.14.2: {} + react-refresh@0.17.0: {} + react-remove-scroll-bar@2.3.8(@types/react@18.3.23)(react@18.3.1): dependencies: react: 18.3.1 @@ -24032,6 +24147,24 @@ snapshots: tsx: 4.20.3 yaml: 2.8.0 + vite@7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.6 + rollup: 4.44.2 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 24.0.10 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.30.1 + sass: 1.89.2 + terser: 5.43.1 + tsx: 4.20.3 + yaml: 2.8.0 + vitest@3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@20.19.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.10.4(@types/node@20.19.4)(typescript@5.8.3))(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2