Skip to content

Commit

Permalink
Provide auto complete suggestions when using matchUtilities
Browse files Browse the repository at this point in the history
  • Loading branch information
thecrypticace authored and RobinMalfait committed Oct 10, 2024
1 parent dc69802 commit b7c8d6e
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 0 deletions.
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')
})

0 comments on commit b7c8d6e

Please sign in to comment.