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: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
2025-12-17 - f1ffdb28fcab2b3a9b07da6336ce1eb9da02a3d0 - components/src/core/components/Input/RadioInput.vue, components/src/core/components/Input/RadioGroup.vue, components/src/core/components/Input/radio-input.scss components/src/core/components/Input/_variables.scss - Add optional secondaryLabel prop to radio buttons for displaying additional information

2025-12-16 - d3b4f1f2553967a334ce5414eb3f83c12d19c368 - components/src/core/components/Icon/icons.ts - Add oxd-slash-circle icon

2025-11-30 - 30957e24f2be744d6a1987c8951e1b083b0a57c5 - components/src/core/components/Icon/icons.ts - Add oxd-swap icon
Expand Down
11 changes: 7 additions & 4 deletions components/src/core/components/Input/RadioGroup.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import {defineComponent, h} from 'vue';
import { defineComponent, h } from 'vue';
import RadioInput from '@orangehrm/oxd/core/components/Input/RadioInput.vue';
import InputGroup from '@orangehrm/oxd/core/components/InputField/InputGroup.vue';
import useTranslate from '../../../composables/useTranslate';
Expand All @@ -11,6 +11,7 @@ export interface State {
export interface Options {
id: number;
label: string;
secondaryLabel?: string;
disabled?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
style?: Record<string, any>;
Expand All @@ -23,7 +24,7 @@ export default defineComponent({
InputGroup,
},

emits: ['update:modelValue', 'blur', 'focus', 'change'],
emits: ['update:modelValue', 'blur', 'focus', 'change', 'click'],

props: {
id: {
Expand All @@ -41,7 +42,8 @@ export default defineComponent({
type: Boolean,
},
options: {
type: Array,
type: Array as () => Options[],
default: () => [],
},
modelValue: {
type: String,
Expand All @@ -51,7 +53,7 @@ export default defineComponent({
render() {
const inputId = this.id == '' ? 'radio-group-id' : this.id;
const inputClass = this.class == '' ? 'radio-column' : this.class;
const {$t} = useTranslate();
const { $t } = useTranslate();
return h(
InputGroup,
{
Expand All @@ -69,6 +71,7 @@ export default defineComponent({
style: option.style,
autofocus: this.$attrs.autofocus && index === 0 ? true : false,
optionLabel: $t(option.label),
secondaryLabel: option.secondaryLabel ? $t(option.secondaryLabel) : '',
value: option.id,
modelValue: this.modelValue,
disabled: this.disabled ? 'true' : option.disabled,
Expand Down
30 changes: 17 additions & 13 deletions components/src/core/components/Input/RadioInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,29 @@
<template v-if="labelPosition === 'left'">
<div class="oxd-radio-option-label">
{{ optionLabel }}
<span v-if="secondaryLabel" class="oxd-radio-option-secondary-label">
{{ secondaryLabel }}
</span>
</div>
</template>
<input
type="radio"
@focus="onFocus"
@blur="onBlur"
@change="onChange"
v-bind="$attrs"
v-model="checked"
:disabled="disabled"
/>
<input type="radio" @focus="onFocus" @blur="onBlur" @change="onChange" v-bind="$attrs" v-model="checked"
:disabled="disabled" />
<span :class="classes" :style="style" class="oxd-radio-input"></span>
<template v-if="labelPosition === 'right'">
<div class="oxd-radio-option-label">
{{ optionLabel }}
<span v-if="secondaryLabel" class="oxd-radio-option-secondary-label">
{{ secondaryLabel }}
</span>
</div>
</template>
</label>
</div>
</template>

<script lang="ts">
import {defineComponent} from 'vue';
import {Position, LABEL_POSITIONS, RIGHT} from './types';
import { defineComponent } from 'vue';
import { Position, LABEL_POSITIONS, RIGHT } from './types';

export interface State {
focused: boolean;
Expand All @@ -47,10 +46,14 @@ export default defineComponent({
type: String,
default: '',
},
secondaryLabel: {
type: String,
default: '',
},
labelPosition: {
type: String,
default: RIGHT,
validator: function(value: Position) {
validator: function (value: Position) {
return LABEL_POSITIONS.indexOf(value) !== -1;
},
},
Expand Down Expand Up @@ -88,7 +91,8 @@ export default defineComponent({
get() {
return this.modelValue;
},
set(value) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
set(value: any) {
this.checkedProxy = value;
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,21 @@ exports[`FileInput.vue download box is rendered 1`] = `
<div class="oxd-download-box-radio-buttons">
<div class="oxd-radio-wrapper"><label class="">
<!--v-if--><input type="radio" id="check1" value="keep"><span class="oxd-radio-input oxd-radio-input--active --label-right oxd-radio-input"></span>
<div class="oxd-radio-option-label">Keep Current</div>
<div class="oxd-radio-option-label">Keep Current
<!--v-if-->
</div>
</label></div>
<div class="oxd-radio-wrapper"><label class="">
<!--v-if--><input type="radio" id="check2" value="delete"><span class="oxd-radio-input oxd-radio-input--active --label-right oxd-radio-input"></span>
<div class="oxd-radio-option-label">Delete Current</div>
<div class="oxd-radio-option-label">Delete Current
<!--v-if-->
</div>
</label></div>
<div class="oxd-radio-wrapper"><label class="">
<!--v-if--><input type="radio" id="check3" value="replace"><span class="oxd-radio-input oxd-radio-input--active --label-right oxd-radio-input"></span>
<div class="oxd-radio-option-label">Replace Current</div>
<div class="oxd-radio-option-label">Replace Current
<!--v-if-->
</div>
</label></div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ exports[`RadioGroup > RadioGroup.vue renders OXD RadioGroup 1`] = `
<div class="radio-column">
<div class="oxd-radio-wrapper"><label class="">
<!--v-if--><input type="radio" id="radio-group-id_1" name="radio-group-id" value="1"><span class="oxd-radio-input oxd-radio-input--active --label-right oxd-radio-input"></span>
<div class="oxd-radio-option-label">Item one</div>
<div class="oxd-radio-option-label">Item one
<!--v-if-->
</div>
</label></div>
<div class="oxd-radio-wrapper"><label class="">
<!--v-if--><input type="radio" id="radio-group-id_2" name="radio-group-id" value="2"><span class="oxd-radio-input oxd-radio-input--active --label-right oxd-radio-input"></span>
<div class="oxd-radio-option-label">Item two</div>
<div class="oxd-radio-option-label">Item two
<!--v-if-->
</div>
</label></div>
</div>
<!--v-if-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
exports[`RadioInput.vue renders OXD Radio Input 1`] = `
<div class="oxd-radio-wrapper"><label class="">
<!--v-if--><input type="radio" label="Radio"><span class="oxd-radio-input oxd-radio-input--active --label-right oxd-radio-input"></span>
<div class="oxd-radio-option-label"></div>
<div class="oxd-radio-option-label">
<!--v-if-->
</div>
</label></div>
`;
80 changes: 80 additions & 0 deletions components/src/core/components/Input/__tests__/radio-group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,84 @@ describe('RadioGroup > RadioGroup.vue', () => {
expect(wrapper.find('.radio-new-class').exists()).toBeTruthy();
expect(wrapper.find('#radio-new-id_1').exists()).toBeTruthy();
});

it('should render secondary labels for radio options', () => {
const optionsWithSecondaryLabel = [
{
id: 1,
label: 'Full Time',
secondaryLabel: '(Permanent)',
},
{
id: 2,
label: 'Part Time',
secondaryLabel: '(Temporary)',
},
{
id: 3,
label: 'Contract',
},
];

const wrapper = mount(RadioGroup, {
props: {
options: optionsWithSecondaryLabel,
},
});

// Check if secondary labels are rendered as provided
expect(wrapper.text()).toContain('(Permanent)');
expect(wrapper.text()).toContain('(Temporary)');

// Check if all secondary label elements exist for options with secondaryLabel
const secondaryLabels = wrapper.findAll(
'.oxd-radio-option-secondary-label',
);
expect(secondaryLabels).toHaveLength(2);
});

it('should not render secondary label when not provided', () => {
const wrapper = mount(RadioGroup, {
props: {
options: optionsList,
},
});

const secondaryLabels = wrapper.findAll(
'.oxd-radio-option-secondary-label',
);
expect(secondaryLabels).toHaveLength(0);
});

it('each radio field should contain correct secondary label', () => {
const optionsWithSecondaryLabel = [
{
id: 1,
label: 'Option One',
secondaryLabel: '(Label One)',
},
{
id: 2,
label: 'Option Two',
secondaryLabel: '{Label Two}',
},
];

const wrapper = mount(RadioGroup, {
props: {
options: optionsWithSecondaryLabel,
},
});

const radioWrappers = wrapper.findAll('.oxd-radio-wrapper');
expect(radioWrappers).toHaveLength(2);

// First radio should have secondary label as provided
expect(radioWrappers[0].text()).toContain('Option One');
expect(radioWrappers[0].text()).toContain('(Label One)');

// Second radio should have secondary label as provided
expect(radioWrappers[1].text()).toContain('Option Two');
expect(radioWrappers[1].text()).toContain('{Label Two}');
});
});
37 changes: 37 additions & 0 deletions components/src/core/components/Input/__tests__/radio-input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,41 @@ describe('RadioInput.vue', () => {
'oxd-radio-input--focus',
);
});

it('should render secondary label as provided by developer', () => {
const wrapper = mount(RadioInput, {
props: {
optionLabel: 'Full Time',
secondaryLabel: '(Permanent)',
},
});
expect(wrapper.text()).toContain('Full Time');
expect(wrapper.text()).toContain('(Permanent)');
expect(
wrapper.find('.oxd-radio-option-secondary-label').exists(),
).toBeTruthy();
});

it('should not render secondary label when not provided', () => {
const wrapper = mount(RadioInput, {
props: {
optionLabel: 'Full Time',
},
});
expect(wrapper.text()).toContain('Full Time');
expect(
wrapper.find('.oxd-radio-option-secondary-label').exists(),
).toBeFalsy();
});

it('should render secondary label with any format passed by developer', () => {
const wrapper = mount(RadioInput, {
props: {
optionLabel: 'Part Time',
secondaryLabel: '{Temporary}',
},
});
expect(wrapper.text()).toContain('Part Time');
expect(wrapper.text()).toContain('{Temporary}');
});
});
1 change: 1 addition & 0 deletions components/src/core/components/Input/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ $oxd-radio-active-border: $oxd-border $oxd-interface-gray-lighten-1-color !defau
$oxd-radio-checked-active-border: $oxd-border $oxd-radio-checked-color !default;
$oxd-radio-checked-disabled-border: $oxd-border
$oxd-radio-checked-disabled-color !default;
$oxd-radio-secondary-label-opacity: 0.6 !default;

$oxd-dropdown-text-padding: 0 0.25rem 0 0.5rem !default;
$oxd-dropdown-animation-time: 300ms !default;
Expand Down
7 changes: 5 additions & 2 deletions components/src/core/components/Input/radio-input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
@import 'variables';

.oxd-radio-wrapper {

.oxd-radio-option-label{
.oxd-radio-option-label {
overflow-wrap: break-word;
word-break: break-all;
}

.oxd-radio-option-secondary-label {
opacity: $oxd-radio-secondary-label-opacity;
}

& label {
cursor: pointer;
display: flex;
Expand Down
Loading