Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
cfc47e4
feat: proof-of-concept TUI button integration in Fields
juliacanzani Feb 19, 2026
a0c4084
refactor: first theming pass with centralized wp theme
juliacanzani Feb 19, 2026
fcc2e7e
feat: finish TUI button POC follow-ups and Storybook environment cont…
juliacanzani Feb 21, 2026
7dfe9fd
Remove TUI input-group overrides from text field styles
juliacanzani Feb 24, 2026
6ef998a
Refine Button API, fix import paths, update deps and config
juliacanzani Feb 24, 2026
0ab9713
Add ProseMirror dynamic text editor, settings modal, and utilities
juliacanzani Feb 24, 2026
6a6cb62
Refactor Text and Textarea fields with three-path rendering
juliacanzani Feb 24, 2026
9634e21
Make sure tui styles are available and enqueued
nicolas-jaussaud Mar 3, 2026
5f795b5
feat(utils): add shared isDev() utility
juliacanzani Mar 5, 2026
b11d6cb
fix(Button): replace module-level warning flags with useRef guards an…
juliacanzani Mar 5, 2026
68c7427
fix(Button): move destructive→danger normalisation before branch spli…
juliacanzani Mar 5, 2026
9d766e8
feat(Text): add FieldsTextProps interface and type component signature
juliacanzani Mar 5, 2026
5dd6180
fix(Text): add mountedRef guard to prevent onChange firing on mount
juliacanzani Mar 5, 2026
cd38bb9
feat(Text): extract DynamicTextField sub-component, add forwardRef
juliacanzani Mar 5, 2026
1f0c7df
fix(Text): remove querySelector in inputMaskRef callback, add Field.H…
juliacanzani Mar 5, 2026
795f1e3
test(Button,Text): add TUI render path coverage
juliacanzani Mar 5, 2026
ca57b45
fix(jest): support TUI ESM packages and TypeScript in test suite
juliacanzani Mar 5, 2026
2a74fb4
chore: add TUI migration harness infrastructure
juliacanzani Mar 5, 2026
63dbdcf
feat(Checkbox): migrate to TUI Checkbox with forwardRef and Field com…
juliacanzani Mar 5, 2026
1fc0a42
fix(Checkbox): replace useState/useEffect with TUI controlled pattern
juliacanzani Mar 5, 2026
62f933e
feat(Switch): migrate to TUI Switch with forwardRef and Field compound
juliacanzani Mar 5, 2026
7f6b8ca
fix(Switch): replace useState/useEffect with TUI controlled pattern
juliacanzani Mar 5, 2026
ae3321f
feat(RadioGroup): migrate to TUI RadioGroup with forwardRef and Field…
juliacanzani Mar 5, 2026
0f7be93
feat(Radio): migrate to TUI Radio with forwardRef and FieldsRadioProps
juliacanzani Mar 5, 2026
54ab8f8
feat(Textarea): migrate to TUI Textarea with forwardRef and Field com…
juliacanzani Mar 5, 2026
3ef837e
fix(Textarea): add mountedRef guard to DynamicTextarea to prevent onC…
juliacanzani Mar 5, 2026
2b2ed5a
feat(Notice): migrate to TUI Notice with forwardRef and type→theme ma…
juliacanzani Mar 5, 2026
bb14a51
test(TUI): add TUI render path tests for Checkbox, Switch, Radio, Tex…
juliacanzani Mar 6, 2026
b4b305b
chore: add harness batch 2 specs for Checkbox, Switch, Radio, Textare…
juliacanzani Mar 6, 2026
3df055b
chore: bump @tangible/ui to 0.0.8 and add new peer dependencies
juliacanzani Mar 18, 2026
c829037
Update .gitignore + remove uneeded files
nicolas-jaussaud Mar 24, 2026
27f6d98
Button: Click event - Fix compatibility issue with react-aria hooks
nicolas-jaussaud Mar 24, 2026
4ae0736
Popover: Avoid issue with button not closing Popover
nicolas-jaussaud Mar 24, 2026
28ebade
RadioGroup: Add labelVisuallyHidden prop
nicolas-jaussaud Mar 24, 2026
8e9578f
Test: Jest - Update tests according to TUI migration
nicolas-jaussaud Mar 25, 2026
e245791
Build scripts
nicolas-jaussaud Mar 25, 2026
a15a4b1
Store: setValue - Remove changes from last commit, pass onChange to R…
nicolas-jaussaud Mar 25, 2026
4a570fa
Storybook: Minimize circular dependencies to avoid issues with vite
nicolas-jaussaud Mar 25, 2026
fca3f90
Render: Alias renderField and renderElement in index export (can be u…
nicolas-jaussaud Mar 25, 2026
6b21f22
Storybook: Add accordion component - closes #13
nicolas-jaussaud Mar 25, 2026
06e0233
Accordion: Style - Avoid some rules overwritten when used inside word…
nicolas-jaussaud Mar 25, 2026
4973fd9
Dimensions: Move out from dashicon for link/unlink button so that it …
nicolas-jaussaud Mar 25, 2026
72a3d84
Storybook: Add dimensions component - closes #22
nicolas-jaussaud Mar 25, 2026
2ea7cff
Storybook: Add border component - closes #15
nicolas-jaussaud Mar 25, 2026
0e6df6b
ComboBox: Minor style tweaks after switching to tui buttons
nicolas-jaussaud Mar 30, 2026
36a7e26
Storybook: Add comboxbox component - closes #20
nicolas-jaussaud Mar 30, 2026
46e623f
Storybook: Add date component - closes #21
nicolas-jaussaud Mar 30, 2026
47d2321
Date: Minor style tweaks after switching to tui buttons
nicolas-jaussaud Mar 30, 2026
5e4abb0
Storybook: Add file component - closes #26
nicolas-jaussaud Mar 30, 2026
a0b96bb
File: Minor style tweaks after switching to tui buttons
nicolas-jaussaud Mar 30, 2026
13a672f
Gallery: Minor style tweaks after switching to tui buttons
nicolas-jaussaud Mar 30, 2026
feb8a05
Storybook: Add gradient component - closes #28
nicolas-jaussaud Mar 30, 2026
997ed6f
Storybook: Add number component - closes #31
nicolas-jaussaud Mar 30, 2026
5dd405a
Select: Minor style tweaks after switching to tui
nicolas-jaussaud Mar 30, 2026
c03bde1
Storybook: Add select component - closes #33
nicolas-jaussaud Mar 30, 2026
6378b24
Storybook: Add simple diemension component - closes #34
nicolas-jaussaud Mar 30, 2026
da2cc12
Storybook: Add tab component - closes #36
nicolas-jaussaud Mar 30, 2026
e3ef603
Tab: Minor style tweaks after switching to tui buttons
nicolas-jaussaud Mar 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
node_modules
*.log
*.local.js
*.tsbuildinfo
.env
.env-*
.DS_Store
.idea/
.rollup-plugin-visualizer.html
.gitignore

# Ignore lock files to allow any package manager
bun.lock
Expand All @@ -26,3 +28,7 @@ yarn.lock
*.swp

*storybook.log

.harness/
.harness.yml
.claude
97 changes: 87 additions & 10 deletions .storybook/decorators/context.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,92 @@
import React from 'react'
import { useEffect, useState } from 'react'
import type { Decorator } from '@storybook/react-vite'

export const withContext = (Story, { args, globals }) => {
// Access the global context value
const globalContext = globals.context || 'default';
type Mode = 'light' | 'dark' | 'auto'

// Generate a dynamic class name based on context
const contextClass = `tf-context-${globalContext}`;
type ContextFrameProps = {
Story: any
contextClass: string
modeClass: string
themeAttr?: 'dark'
colorScheme?: 'light' | 'dark'
}

const ContextFrame = ({
Story,
contextClass,
modeClass,
themeAttr,
colorScheme
}: ContextFrameProps) => {
return (
<div className={contextClass}>
<Story {...args} />
<div className={`${contextClass} ${modeClass}`} style={colorScheme ? { colorScheme } : undefined}>
<div className="tui-interface" data-theme={themeAttr}>
<Story />
</div>
</div>
);
};
)
}

export const withContext: Decorator = (Story, { globals, viewMode, id }) => {
const contextClass = `tf-context-${globals.context || 'default'}`
const colorMode = (globals.colorMode || 'light') as Mode
const [systemMode, setSystemMode] = useState<'light' | 'dark'>(() => {
if (typeof window === 'undefined' || !window.matchMedia) return 'light'
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
})

useEffect(() => {
if (colorMode !== 'auto' || typeof window === 'undefined' || !window.matchMedia) return

const media = window.matchMedia('(prefers-color-scheme: dark)')
const onChange = (event: MediaQueryListEvent) => {
setSystemMode(event.matches ? 'dark' : 'light')
}
const legacyOnChange = (event: MediaQueryListEvent) => onChange(event)

setSystemMode(media.matches ? 'dark' : 'light')
if ('addEventListener' in media) {
media.addEventListener('change', onChange)
} else {
;(media as MediaQueryList & {
addListener: (listener: (event: MediaQueryListEvent) => void) => void
}).addListener(legacyOnChange)
}

return () => {
if ('removeEventListener' in media) {
media.removeEventListener('change', onChange)
} else {
;(media as MediaQueryList & {
removeListener: (listener: (event: MediaQueryListEvent) => void) => void
}).removeListener(legacyOnChange)
}
}
}, [colorMode])

const resolvedMode = colorMode === 'auto' ? systemMode : colorMode
const modeClass =
colorMode === 'auto'
? `tf-color-mode-auto tf-color-mode-${resolvedMode}`
: `tf-color-mode-${resolvedMode}`
const themeAttr = resolvedMode === 'dark' ? 'dark' : undefined
const colorScheme = resolvedMode
const isDocsView = viewMode === 'docs'
const isDocsPage = typeof id === 'string' && id.endsWith('--docs')

// Keep docs page chrome (controls/table) unwrapped to avoid style leakage,
// but still wrap docs story blocks so components render with TUI styles.
if (isDocsView && isDocsPage) {
return <Story />
}

return (
<ContextFrame
Story={Story}
contextClass={contextClass}
modeClass={modeClass}
themeAttr={themeAttr}
colorScheme={colorScheme}
/>
)
}
149 changes: 149 additions & 0 deletions .storybook/decorators/globalCss.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { useEffect, useMemo, useState } from 'react'
import type { Decorator } from '@storybook/react-vite'

type GlobalCssMode =
| 'auto'
| 'none'
| 'basic'
| 'wordpress'
| 'elementor'
| 'beaver-builder'

type Direction = 'ltr' | 'rtl'
type ColorMode = 'light' | 'dark' | 'auto'

const WORDPRESS_STYLES = [
'https://wordpress.org/gutenberg/wp-admin/css/common.min.css',
'https://wordpress.org/gutenberg/wp-admin/css/forms.min.css',
'https://wordpress.org/gutenberg/wp-includes/css/dashicons.min.css'
]

const resolveMode = (globals: Record<string, unknown>): Exclude<GlobalCssMode, 'auto'> => {
const selected = (globals.css as GlobalCssMode | undefined) ?? 'auto'
if (selected !== 'auto') return selected

const context = (globals.context as string | undefined) ?? 'default'
if (context === 'wp') return 'wordpress'
if (context === 'elementor') return 'elementor'
if (context === 'beaver-builder') return 'beaver-builder'

return 'basic'
}

const getElementorMedia = (mode: ColorMode) => {
if (mode === 'dark') return 'all'
if (mode === 'light') return 'none'
return '(prefers-color-scheme: dark)'
}

const getBodyClasses = (
mode: Exclude<GlobalCssMode, 'auto'>,
direction: Direction,
resolvedColorMode: 'light' | 'dark'
) => {
const classes: string[] = []

if (mode === 'wordpress') classes.push('wp-admin', 'wp-core-ui')
if (mode === 'elementor') classes.push('elementor-editor-active')
if (mode === 'beaver-builder') {
classes.push('fl-builder', 'fl-builder-admin')
classes.push(resolvedColorMode === 'dark' ? 'fl-builder-ui-skin--dark' : 'fl-builder-ui-skin--light')
}
if (direction === 'rtl') classes.push('rtl')

return classes
}

export const withGlobalCss: Decorator = (Story, context) => {
const globals = context.globals as Record<string, unknown>
const isDocsView = context.viewMode === 'docs'
const mode = resolveMode(globals)
const direction = ((globals.direction as Direction | undefined) ?? 'ltr') as Direction
const colorMode = ((globals.colorMode as ColorMode | undefined) ?? 'light') as ColorMode
const [systemMode, setSystemMode] = useState<'light' | 'dark'>(() => {
if (typeof window === 'undefined' || !window.matchMedia) return 'light'
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
})

useEffect(() => {
if (colorMode !== 'auto' || typeof window === 'undefined' || !window.matchMedia) return

const media = window.matchMedia('(prefers-color-scheme: dark)')
const onChange = (event: MediaQueryListEvent) => {
setSystemMode(event.matches ? 'dark' : 'light')
}
const legacyOnChange = (event: MediaQueryListEvent) => onChange(event)

setSystemMode(media.matches ? 'dark' : 'light')
if ('addEventListener' in media) {
media.addEventListener('change', onChange)
} else {
;(media as MediaQueryList & {
addListener: (listener: (event: MediaQueryListEvent) => void) => void
}).addListener(legacyOnChange)
}

return () => {
if ('removeEventListener' in media) {
media.removeEventListener('change', onChange)
} else {
;(media as MediaQueryList & {
removeListener: (listener: (event: MediaQueryListEvent) => void) => void
}).removeListener(legacyOnChange)
}
}
}, [colorMode])

const resolvedColorMode = colorMode === 'auto' ? systemMode : colorMode

const externalStyles = !isDocsView && mode === 'wordpress' ? WORDPRESS_STYLES : []
const wrapperClasses = useMemo(() => {
if (isDocsView) return []
if (mode === 'wordpress') return ['wp-admin', 'wp-core-ui']
if (mode === 'elementor') return ['elementor-editor-active']
if (mode === 'beaver-builder') {
return [resolvedColorMode === 'dark' ? 'fl-builder-ui-skin--dark' : 'fl-builder-ui-skin--light']
}
return []
}, [isDocsView, mode, resolvedColorMode])

useEffect(() => {
if (isDocsView) return

const { body } = document
const bodyClasses = getBodyClasses(mode, direction, resolvedColorMode)
body.classList.add('tui-interface', `tf-color-mode-${resolvedColorMode}`)
if (resolvedColorMode === 'dark') {
body.setAttribute('data-theme', 'dark')
} else {
body.removeAttribute('data-theme')
}
bodyClasses.forEach((className) => body.classList.add(className))

let elementorMarker: HTMLStyleElement | null = null
if (mode === 'elementor') {
elementorMarker = document.createElement('style')
elementorMarker.id = 'e-theme-ui-dark-css'
elementorMarker.media = getElementorMedia(colorMode)
body.appendChild(elementorMarker)
}

return () => {
body.classList.remove('tui-interface', 'tf-color-mode-light', 'tf-color-mode-dark')
bodyClasses.forEach((className) => body.classList.remove(className))
body.removeAttribute('data-theme')
if (elementorMarker && elementorMarker.parentElement) {
elementorMarker.parentElement.removeChild(elementorMarker)
}
}
}, [isDocsView, mode, direction, resolvedColorMode, colorMode])

return (
<div className={wrapperClasses.join(' ')}>
{externalStyles.map((stylesheet) => (
<link key={stylesheet} rel="stylesheet" href={stylesheet} />
))}
<Story />
</div>
)
}
23 changes: 23 additions & 0 deletions .storybook/decorators/rtl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect } from 'react'
import type { Decorator } from '@storybook/react-vite'

type Direction = 'ltr' | 'rtl'

export const withRtl: Decorator = (Story, context) => {
const direction = ((context.globals.direction as Direction | undefined) ?? 'ltr') as Direction

useEffect(() => {
const root = document.documentElement
root.setAttribute('dir', direction)
return () => {
root.setAttribute('dir', 'ltr')
}
}, [direction])

return (
<div dir={direction}>
<Story />
</div>
)
}

36 changes: 20 additions & 16 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import type { StorybookConfig } from '@storybook/react-vite'


/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
"stories": [
"../assets/src/components/**/*.mdx",
"../assets/src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)"
const config: StorybookConfig = {
stories: [
'../assets/src/components/**/*.mdx',
'../assets/src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)'
],
"addons": [
"@storybook/addon-onboarding",
"@storybook/addon-docs"
addons: [
'@storybook/addon-onboarding',
'@storybook/addon-docs',
'@storybook/addon-a11y'
],
"framework": {
"name": "@storybook/react-vite",
"options": {}
framework: {
name: '@storybook/react-vite',
options: {}
},
typescript: {
reactDocgen: false
},
core: {
builder: '@storybook/builder-vite', // Replace Webpack with Vite
disableTelemetry: true,
builder: '@storybook/builder-vite',
disableTelemetry: true
}
};
export default config;
}

export default config
11 changes: 5 additions & 6 deletions .storybook/manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { addons } from 'storybook/manager-api';
import { themes } from 'storybook/theming';
import tangibleTheme from './tangibleTheme';

import { addons } from 'storybook/manager-api'
import tangibleTheme from './tangibleTheme'

addons.setConfig({
theme: tangibleTheme,
});
theme: tangibleTheme
})
35 changes: 35 additions & 0 deletions .storybook/preview.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
:root {
font-family: "Open Sans", "Inter", "Helvetica Neue", Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

body {
margin: 0;
min-width: 320px;
}

body.tui-interface {
min-height: 100vh;
background-color: var(--tui-color-bg);
color: var(--tui-color-fg);
}

body.tui-interface.tf-color-mode-dark {
color-scheme: dark;
}

body.tui-interface.tf-color-mode-light {
color-scheme: light;
}

/* Docs story preview: wrapper fills frame */
.docs-story .tui-interface {
min-height: 100%;
padding: 1rem;
box-sizing: border-box;
}
Loading
Loading