Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion bestax-mcp/data/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,7 @@
"import": "import { useBulmaClasses } from '@allxsmith/bestax-bulma';",
"compound": false,
"propCount": 0,
"exampleCount": 40
"exampleCount": 43
},
{
"name": "usePrefixedClassNames",
Expand Down
2 changes: 1 addition & 1 deletion bestax-mcp/data/components/Valid value constants.json

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion bestax-mcp/data/components/useBulmaClasses.json

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions bulma-ui/src/helpers/__tests__/useFlexboxClasses.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,30 @@ describe('useFlexboxClasses', () => {
).toBe('is-flex-wrap-wrap');
});

it.each([
['displayTouch', 'flex'],
['displayTabletOnly', 'inline-flex'],
['displayDesktopOnly', 'flex'],
['displayWidescreenOnly', 'inline-flex'],
] as const)('opens the container gate via %s %s', (prop, value) => {
expect(
renderUseFlexboxClasses({
[prop]: value,
flexDirection: 'row',
})
).toBe('is-flex-direction-row');
});

it('keeps the container gate closed for a non-flex -only/touch display', () => {
expect(
renderUseFlexboxClasses({
displayTouch: 'block',
displayTabletOnly: 'grid',
flexDirection: 'row',
})
).toBe('');
});

it('emits flex item classes without any display prop', () => {
expect(
renderUseFlexboxClasses({
Expand Down
23 changes: 23 additions & 0 deletions bulma-ui/src/helpers/__tests__/useTypographyClasses.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,29 @@ describe('useTypographyClasses', () => {
).toBe('is-size-3-tablet has-text-centered-tablet is-uppercase');
});

it('suffixes textSize with touch (a size-supported viewport)', () => {
expect(
renderUseTypographyClasses({
textSize: '3',
textAlign: 'left',
viewport: 'touch',
})
).toBe('is-size-3-touch has-text-left-touch');
});

it.each(['tablet-only', 'desktop-only', 'widescreen-only'] as const)(
'drops the %s suffix from textSize (Bulma ships no is-size-*-only) while keeping it on textAlign',
viewport => {
expect(
renderUseTypographyClasses({
textSize: '3',
textAlign: 'left',
viewport,
})
).toBe(`is-size-3 has-text-left-${viewport}`);
}
);

it('ignores an invalid viewport (no suffix applied)', () => {
expect(
renderUseTypographyClasses({
Expand Down
90 changes: 89 additions & 1 deletion bulma-ui/src/helpers/__tests__/useVisibilityClasses.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,12 @@ describe('useVisibilityClasses', () => {
);
});

it('applies display grid class', () => {
expect(renderUseVisibilityClasses({ display: 'grid' })).toBe('is-grid');
});

it('ignores invalid display values', () => {
expect(renderUseVisibilityClasses({ display: 'grid' as any })).toBe('');
expect(renderUseVisibilityClasses({ display: 'flexbox' as any })).toBe('');
Comment thread
allxsmith marked this conversation as resolved.
});

it('ignores invalid visibility values', () => {
Expand All @@ -117,4 +121,88 @@ describe('useVisibilityClasses', () => {
)
).toBe('bulma-is-hidden-mobile bulma-is-flex');
});

it('maps displayTouch none to is-hidden-touch', () => {
expect(renderUseVisibilityClasses({ displayTouch: 'none' })).toBe(
'is-hidden-touch'
);
});

it('applies displayTouch class', () => {
expect(renderUseVisibilityClasses({ displayTouch: 'flex' })).toBe(
'is-flex-touch'
);
});

it('applies displayTabletOnly class', () => {
expect(renderUseVisibilityClasses({ displayTabletOnly: 'block' })).toBe(
'is-block-tablet-only'
);
});

it('applies displayDesktopOnly class', () => {
expect(renderUseVisibilityClasses({ displayDesktopOnly: 'flex' })).toBe(
'is-flex-desktop-only'
);
});

it('applies displayWidescreenOnly class', () => {
expect(
renderUseVisibilityClasses({ displayWidescreenOnly: 'inline-flex' })
).toBe('is-inline-flex-widescreen-only');
});

it('applies visibilityTouch hidden as is-hidden-touch', () => {
expect(renderUseVisibilityClasses({ visibilityTouch: 'hidden' })).toBe(
'is-hidden-touch'
);
});

it('applies visibilityTabletOnly invisible as is-invisible-tablet-only', () => {
expect(
renderUseVisibilityClasses({ visibilityTabletOnly: 'invisible' })
).toBe('is-invisible-tablet-only');
});

it('applies visibilityDesktopOnly sr-only as is-sr-only-desktop-only', () => {
expect(
renderUseVisibilityClasses({ visibilityDesktopOnly: 'sr-only' })
).toBe('is-sr-only-desktop-only');
});

it('applies visibilityWidescreenOnly hidden as is-hidden-widescreen-only', () => {
expect(
renderUseVisibilityClasses({ visibilityWidescreenOnly: 'hidden' })
).toBe('is-hidden-widescreen-only');
});

it('suffixes display with touch and -only viewports via the legacy viewport prop', () => {
expect(
renderUseVisibilityClasses({ display: 'flex', viewport: 'touch' })
).toBe('is-flex-touch');
expect(
renderUseVisibilityClasses({ display: 'block', viewport: 'tablet-only' })
).toBe('is-block-tablet-only');
expect(
renderUseVisibilityClasses({ display: 'block', viewport: 'desktop-only' })
).toBe('is-block-desktop-only');
expect(
renderUseVisibilityClasses({
display: 'block',
viewport: 'widescreen-only',
})
).toBe('is-block-widescreen-only');
});

it('suffixes visibility with touch and -only viewports via the legacy viewport prop', () => {
expect(
renderUseVisibilityClasses({ visibility: 'hidden', viewport: 'touch' })
).toBe('is-hidden-touch');
expect(
renderUseVisibilityClasses({
visibility: 'invisible',
viewport: 'desktop-only',
})
).toBe('is-invisible-desktop-only');
});
});
26 changes: 24 additions & 2 deletions bulma-ui/src/helpers/bulmaClassHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export const validDisplays = [
'inline',
'inline-block',
'inline-flex',
'grid',
] as const;

/**
Expand Down Expand Up @@ -237,8 +238,12 @@ export const validFlexGrowShrink = ['0', '1', '2', '3', '4', '5'] as const;
export const validViewports = [
'mobile',
'tablet',
'tablet-only',
'touch',
'desktop',
'desktop-only',
'widescreen',
'widescreen-only',
Comment thread
claude[bot] marked this conversation as resolved.
'fullhd',
] as const;

Expand All @@ -261,10 +266,18 @@ export interface BulmaDisplayProps {
displayMobile?: (typeof validDisplays)[number] | 'none';
/** Display type for tablet viewport (769px - 1023px). */
displayTablet?: (typeof validDisplays)[number] | 'none';
/** Display type for tablet-only viewport (769px - 1023px, excludes desktop+). */
displayTabletOnly?: (typeof validDisplays)[number] | 'none';
/** Display type for touch viewport (up to 1023px; mobile + tablet). */
displayTouch?: (typeof validDisplays)[number] | 'none';
/** Display type for desktop viewport (1024px - 1215px). */
displayDesktop?: (typeof validDisplays)[number] | 'none';
/** Display type for desktop-only viewport (1024px - 1215px, excludes widescreen+). */
displayDesktopOnly?: (typeof validDisplays)[number] | 'none';
/** Display type for widescreen viewport (1216px - 1407px). */
displayWidescreen?: (typeof validDisplays)[number] | 'none';
/** Display type for widescreen-only viewport (1216px - 1407px, excludes fullhd). */
displayWidescreenOnly?: (typeof validDisplays)[number] | 'none';
/** Display type for fullhd viewport (1408px and above). */
displayFullhd?: (typeof validDisplays)[number] | 'none';
}
Expand All @@ -291,15 +304,24 @@ export const createBulmaClassHelpers = (
};

// Helper to add class with optional viewport
//
// `supportedViewports` narrows which viewports may be suffixed onto this
// class. It defaults to every viewport, but a few Bulma helper families
// (notably `is-size-*`, which ships no `-only` bands) support only a subset,
// so passing the emitting set keeps us from producing dead classes.
const addClass = (
prefix: string,
value: string | undefined,
validValues: readonly string[],
supportsViewport = false
supportsViewport = false,
supportedViewports: readonly string[] = validViewports
) => {
if (value && validValues.includes(value)) {
const className =
supportsViewport && viewport && validViewports.includes(viewport)
supportsViewport &&
viewport &&
validViewports.includes(viewport) &&
supportedViewports.includes(viewport)
? `${prefix}-${value}-${viewport}`
: `${prefix}-${value}`;
addPrefixedClass(className);
Expand Down
93 changes: 92 additions & 1 deletion bulma-ui/src/helpers/useBulmaClasses.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,17 @@ const meta: Meta<typeof UseBulmaClassesDemo> = {
},
viewport: {
control: 'select',
options: ['mobile', 'tablet', 'desktop', 'widescreen', 'fullhd'],
options: [
'mobile',
'tablet',
'tablet-only',
'touch',
'desktop',
'desktop-only',
'widescreen',
'widescreen-only',
'fullhd',
],
},
skeleton: {
control: 'boolean',
Expand Down Expand Up @@ -1723,6 +1733,87 @@ export const ViewportSpecificVisibility: Story = {
name: 'Viewport-Specific Visibility',
};

export const TouchAndOnlyViewports: Story = {
render: () => (
<Box>
<Title size="5">Touch and &quot;-only&quot; Viewports</Title>
<SubTitle size="6">
Resize your browser to see the effect — <code>touch</code> covers mobile
+ tablet, while the <code>-only</code> variants isolate a single
breakpoint band.
</SubTitle>

<Block>
<Title size="6">displayTouch / displayDesktopOnly</Title>
<Box
displayTouch="none"
displayDesktopOnly="flex"
bgColor="info"
colorShade="00"
p="4"
>
Hidden on mobile + tablet (<code>displayTouch=&quot;none&quot;</code>
), shown as flex on desktop only (
<code>displayDesktopOnly=&quot;flex&quot;</code>).
</Box>
</Block>

<Block>
<Title size="6">visibilityTouch / visibilityWidescreenOnly</Title>
<Buttons>
<Button visibilityTouch="hidden">Hidden on mobile + tablet</Button>
<Button visibilityWidescreenOnly="invisible">
Invisible on widescreen only
</Button>
<Button>Always visible</Button>
</Buttons>
</Block>

<Block>
<Title size="6">displayTabletOnly</Title>
<Box displayTabletOnly="block" bgColor="warning" colorShade="05" p="4">
<code>is-block-tablet-only</code> sets <code>display: block</code>{' '}
only inside the tablet-only range (769px - 1023px). A Box is already
block by default, so outside that range it keeps its normal block
display — the <code>-only</code> class does not hide it elsewhere.
</Box>
</Block>

<Content>
<strong>Generated classes:</strong>
<code>
{' '}
is-hidden-touch is-flex-desktop-only is-hidden-touch
is-invisible-widescreen-only is-block-tablet-only
</code>
</Content>
</Box>
),
name: 'Touch and -only Viewports',
};

export const GridDisplay: Story = {
render: () => (
<Box>
<Title size="5">Grid Display</Title>
<SubTitle size="6">
<code>display=&quot;grid&quot;</code> emits <code>is-grid</code>
</SubTitle>
<Box display="grid">
<Notification color="primary" m="1">
Cell 1
</Notification>
<Notification color="info" m="1">
Cell 2
</Notification>
<Notification color="success" m="1">
Cell 3
</Notification>
</Box>
</Box>
),
};

export const ViewportSpecificCombined: Story = {
render: () => (
<Box>
Expand Down
Loading
Loading