Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Next Next commit
Provide auto complete suggestions when using matchUtilities
  • Loading branch information
thecrypticace authored and RobinMalfait committed Oct 10, 2024
commit b7c8d6ec304486be0e33bee0a08339e827e44cfa
26 changes: 26 additions & 0 deletions packages/tailwindcss/src/compat/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,32 @@ export function buildPluginApi(
designSystem.utilities.functional(name, compileFn, {
types,
})

designSystem.utilities.suggest(name, () => {
let values = options?.values ?? {}
let valueKeys = new Set<string | null>(Object.keys(values))

// The `__BARE_VALUE__` key is a special key used to ensure bare values
// work even with legacy configs and plugins
valueKeys.delete('__BARE_VALUE__')

// The `DEFAULT` key is represented as `null` in the utility API
if (valueKeys.has('DEFAULT')) {
valueKeys.delete('DEFAULT')
valueKeys.add(null)
}

let modifiers = options?.modifiers ?? {}
let modifierKeys = modifiers === 'any' ? [] : Object.keys(modifiers)

return [
{
supportsNegative: options?.supportsNegativeValues ?? false,
values: Array.from(valueKeys),
modifiers: modifierKeys,
},
]
})
}
},

Expand Down
176 changes: 176 additions & 0 deletions packages/tailwindcss/src/intellisense.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, test } from 'vitest'
import { __unstable__loadDesignSystem } from '.'
import { buildDesignSystem } from './design-system'
import plugin from './plugin'
import { Theme } from './theme'

const css = String.raw
Expand Down Expand Up @@ -174,3 +175,178 @@ test('Utilities, when marked as important, show as important in intellisense', a
]
`)
})

test('Static utilities from plugins are listed in hovers and completions', async () => {
let input = css`
@import 'tailwindcss/utilities';
@plugin "./plugin.js"l;
`

let design = await __unstable__loadDesignSystem(input, {
loadStylesheet: async (_, base) => ({
base,
content: '@tailwind utilities;',
}),
loadModule: async () => ({
base: '',
module: plugin(({ addUtilities }) => {
addUtilities({
'.custom-utility': {
color: 'red',
},
})
}),
}),
})

expect(design.candidatesToCss(['custom-utility'])).toMatchInlineSnapshot(`
[
".custom-utility {
color: red;
}
",
]
`)

expect(design.getClassList().map((entry) => entry[0])).toContain('custom-utility')
})

test('Functional utilities from plugins are listed in hovers and completions', async () => {
let input = css`
@import 'tailwindcss/utilities';
@plugin "./plugin.js"l;
`

let design = await __unstable__loadDesignSystem(input, {
loadStylesheet: async (_, base) => ({
base,
content: '@tailwind utilities;',
}),
loadModule: async () => ({
base: '',
module: plugin(({ matchUtilities }) => {
matchUtilities(
{
'custom-1': (value) => ({
color: value,
}),
},
{
values: {
red: '#ff0000',
green: '#ff0000',
},
},
)

matchUtilities(
{
'custom-2': (value, { modifier }) => ({
color: `${value} / ${modifier ?? '0%'}`,
}),
},
{
values: {
red: '#ff0000',
green: '#ff0000',
},
modifiers: {
'50': '50%',
'75': '75%',
},
},
)

matchUtilities(
{
'custom-3': (value, { modifier }) => ({
color: `${value} / ${modifier ?? '0%'}`,
}),
},
{
values: {
red: '#ff0000',
green: '#ff0000',
},
modifiers: 'any',
},
)
}),
}),
})

expect(design.candidatesToCss(['custom-1-red', 'custom-1-green', 'custom-1-unknown']))
.toMatchInlineSnapshot(`
[
".custom-1-red {
color: #ff0000;
}
",
".custom-1-green {
color: #ff0000;
}
",
null,
]
`)

expect(design.candidatesToCss(['custom-2-red', 'custom-2-green', 'custom-2-unknown']))
.toMatchInlineSnapshot(`
[
".custom-2-red {
color: #ff0000 / 0%;
}
",
".custom-2-green {
color: #ff0000 / 0%;
}
",
null,
]
`)

expect(design.candidatesToCss(['custom-2-red/50', 'custom-2-red/75', 'custom-2-red/unknown']))
.toMatchInlineSnapshot(`
[
".custom-2-red\\/50 {
color: #ff0000 / 50%;
}
",
".custom-2-red\\/75 {
color: #ff0000 / 75%;
}
",
null,
]
`)

let classMap = new Map(design.getClassList())
let classNames = Array.from(classMap.keys())

// matchUtilities without modifiers
expect(classNames).toContain('custom-1-red')
expect(classMap.get('custom-1-red')?.modifiers).toEqual([])

expect(classNames).toContain('custom-1-green')
expect(classMap.get('custom-1-green')?.modifiers).toEqual([])

expect(classNames).not.toContain('custom-1-unknown')

// matchUtilities with a set list of modifiers
expect(classNames).toContain('custom-2-red')
expect(classMap.get('custom-2-red')?.modifiers).toEqual(['50', '75'])

expect(classNames).toContain('custom-2-green')
expect(classMap.get('custom-2-green')?.modifiers).toEqual(['50', '75'])

expect(classNames).not.toContain('custom-2-unknown')

// matchUtilities with a any modifiers
expect(classNames).toContain('custom-3-red')
expect(classMap.get('custom-3-red')?.modifiers).toEqual([])

expect(classNames).toContain('custom-3-green')
expect(classMap.get('custom-3-green')?.modifiers).toEqual([])

expect(classNames).not.toContain('custom-3-unknown')
})